diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..173f8a6a --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +ColumnLimit: 120 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..5645602c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:24 + +# need to run update and install at the same time to prevent stale update layer +RUN apt-get update && apt-get install -y gdb clang-format openjdk-21-jdk maven diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..fb149f0e --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": [], + "customizations": { + "vscode": { + "extensions": [ + "streetsidesoftware.code-spell-checker", + "bierner.markdown-emoji", + "smulyono.reveal", + "ms-azuretools.vscode-docker", + "ms-vscode.cpptools-extension-pack" + ] + } + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..424e53c3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# These files will be normalized on commit (CRLF converted to LF) - asterisk is a wildcard +* text eol=lf + +# These files will NOT be normalized +*.png -text +*.gif -text +*.jpg -text +*.jpeg -text +*.ico -text +*.jar -text +*.class -text diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 00000000..fbfa09d2 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,29 @@ +name: master +on: + push: + branches: + - master +jobs: + unit-test: + name: Unit Test + runs-on: ubuntu-latest + strategy: + matrix: + nodeVersion: [ 20, 22, 24 ] + jdkVersion: [ openjdk9, openjdk10, openjdk11, openjdk21 ] + steps: + - name: Checkout source code + uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.nodeVersion }} + - name: npm ci + run: npm ci + - name: npm run format-cpp + run: npm run format-cpp + - name: npm run format + run: npm run format + - name: npm run lint + run: npm run lint + - name: Unit test + run: npm test diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 00000000..2d20d7f7 --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,30 @@ +name: Pull Requests +on: + pull_request: + types: [opened, edited, reopened, synchronize] + branches: + - master +jobs: + unit-test: + name: Unit Test + runs-on: ubuntu-latest + strategy: + matrix: + nodeVersion: [ 20, 22, 24 ] + jdkVersion: [ openjdk9, openjdk10, openjdk11, openjdk21 ] + steps: + - name: Checkout source code + uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.nodeVersion }} + - name: npm ci + run: npm ci + - name: npm run format-cpp + run: npm run format-cpp + - name: npm run format + run: npm run format + - name: npm run lint + run: npm run lint + - name: Unit test + run: npm test diff --git a/.gitignore b/.gitignore index a9b46eab..633ce199 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ /build/* node_modules *.node -*.sh *.swp .lock* npm-debug.log .idea +node-java.cbp +*.iml +*.kdev4 +*/.kdev_include_paths +CMakeLists.txt +cmake-build-debug/ +hs_err* \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..5c81151b --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +/test +/testIntegration +/.idea +/build diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..60c971ca --- /dev/null +++ b/.prettierignore @@ -0,0 +1,23 @@ +**/*.cpp +**/*.h +**/*.ppt +**/*.jar +**/*.class +**/*.java +**/*.sh +**/*.html +**/*.json +**/*.png +**/*.md +**/*.rule +**/*.rules +**/*.gyp +**/*.yml +**/Dockerfile +.clang-format +.prettierrc +.prettierignore +.npmignore +.gitignore +.gitattributes +LICENSE diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..53fee0e9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "parser": "typescript", + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false, + "printWidth": 120, + "endOfLine": "lf" +} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..5c437f51 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/local/include/node", + "/usr/lib/jvm/java-17-openjdk-amd64/include/", + "/usr/lib/jvm/java-17-openjdk-amd64/include/linux" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8b4e243e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +{ + "cSpell.words": [ + "clazz", + "clazzclazz", + "Ferner", + "jarray", + "javac", + "jboolean", + "jbyte", + "jchar", + "jclass", + "jdouble", + "jfloat", + "jint", + "jlong", + "jmethod", + "jobj", + "jobject", + "jshort", + "jsize", + "jthrowable", + "jvalue", + "Ljava", + "varargs" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 644621ba..3e23b3ec 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Near Infinity Corporation +Copyright (c) 2025 Joe Ferner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 1c57868d..c10cc364 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,328 @@ -# java + + +[![master](https://github.com/joeferner/node-java/actions/workflows/master.yml/badge.svg)](https://github.com/joeferner/node-java/actions/workflows/master.yml) Bridge API to connect with existing Java APIs. -## Installation Linux +[Google Groups Discussion Forum](https://groups.google.com/forum/#!forum/node-java) + +### Other projects that might be helpful + +* [node-java-maven](https://github.com/joeferner/node-java-maven) - manages your node-java classpath by using maven dependency management. +* [ts-java](https://github.com/RedSeal-co/ts-java) - Create TypeScript declaration files for Java packages. + +## Installation ```bash -$ export JAVA_HOME=/usr/local/share/jdk1.6.0_30 $ npm install java ``` -## Installation Windows +Notes: + +* node-gyp requires python 2.x not python 3.x. See https://github.com/TooTallNate/node-gyp/issues/155 for more details. +* If you see an error such as "Call to 'node findJavaHome.js' returned exit status 1" + Try running `node ./scripts/findJavaHome.js` in the node-java directory to see the full failure message. +* If you are having problems finding 'jni.h'. Make sure you have the JDK installed not just the JRE. If you are using + OpenJDK you want the openjdk-7-jdk package, not openjdk-7-jre. _Mavericks users see [Issue #86](https://github.com/nearinfinity/node-java/issues/86) if you run into this._ + +### Installation Ubuntu + +- `sudo apt install make g++` +- If u've error (on global installation): `EACCES user nobody does not have permission to access the dev dir /root/.cache/node-gyp/10.16.0`, then just run: `npm i -g java --unsafe-perm` + +### Installation OSX -* [Install node.js from source](https://github.com/joyent/node/wiki/Installation) (using: vcbuild.bat release) -* The directory where jvm.dll exists must be in the PATH. (e.g. C:\Program Files (x86)\Java\jdk1.6.0_18\jre\bin\client). - This path cannot have quotes. -* Open a Visual Studio command prompt. -* Your Java must be the same architecture as node. By default on windows this is 32-bit. +* If you run into strange runtime issues, it could be because the Oracle JDK does not advertise itself as available for JNI. See [Issue 90](https://github.com/joeferner/node-java/issues/90#issuecomment-45613235) for more details and manual workarounds. If this does occur for you, please update the issue. + +### Installation Windows + +For 64 bit installs with 32 bit node: +* you need the 32 bit JDK, with the 64 bit JDK you will see LNK2001 errormessages (http://stackoverflow.com/questions/10309304/what-library-to-link-to-on-windows-7-for-jni-createjavavm). +* when using the windows SDK 7.1 command prompt (64 bits) be sure to setenv.cmd /Release /x86 + +If you get `ENOENT` errors looking for `\node_modules\node-gyp\..`, ensure you have node-gyp installed as a global nodule: ```bash -$ set PATH=%PATH%;C:\Program Files (x86)\Java\jdk1.6.0_18\jre\bin\client -$ set JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_18\ -$ set NODE_HOME=C:\dev\node-v0.6.8-0 -$ npm install java +npm install -g node-gyp +``` + +If you get `D9025` warnings and `C1083` errors when looking for `.sln` or `.h` files, be sure you've got the `node-gyp`'s dependencies, [as explained here](https://github.com/joeferner/node-java#installation). + +Alternatively, Windows users can easily install all required tools by running the following command in PowerShell as administrator. For more information see [windows-build-tools project page](https://github.com/felixrieseberg/windows-build-tools): + +```sh +npm install --global --production windows-build-tools ``` -## Installation Mac +### Installation ARM (Raspberry Pi) ```bash -$ npm install java +GYP_DEFINES="armv7=0" CCFLAGS='-march=armv6' CXXFLAGS='-march=armv6' npm install java +``` + +## Manual compile (Using node-gyp) + +```bash +./scripts/compile-java.sh +node-gyp configure build +npm test +``` + +_NOTE: You will need node-gyp installed using "npm install -g node-gyp"_ + +On Raspian you might need a: + +* sudo ln -s /usr/lib/jvm/jdk-7-oracle-arm-vfp-hflt /opt/jdk + +Some issues with the OpenSDK7 so take the Oracle version for compiling. + +## Docker + +If you want to play with node-java but don't want to setup the build environment you can run it in docker. + +``` +docker run -it joeferner/node-java bash +``` + +Then inside the docker container create a directory and run + +```bash +npm install --unsafe-perm java ``` +Then create a file called `test.js` with the following contents + +``` +const java = require('java'); +const javaLangSystem = java.import('java.lang.System'); + +javaLangSystem.out.printlnSync('Hello World'); +``` + +Then run + +```bash +node test.js +``` + +## Installation node-webkit + +```bash +npm install -g nw-gyp +npm install java +cd node_modules/java +nw-gyp configure --target=0.10.5 +nw-gyp build +``` + +_See testIntegration/webkit for a working example_ + +## Using node-java in existing maven projects + +When using node-java in existing maven projects, all the dependencies and the class files of the project have to be pushed to the classpath. + +One possible solution would be: + +Issue the command: + +> mvn dependency:copy-dependencies + +Then create the following module javaInit: + +```javascript +"use strict"; +const fs = require("fs"); +const java = require("java"); +const baseDir = "./target/dependency"; +const dependencies = fs.readdirSync(baseDir); + +dependencies.forEach(function(dependency){ + java.classpath.push(baseDir + "/" + dependency); +}) + +java.classpath.push("./target/classes"); +java.classpath.push("./target/test-classes"); + +exports.getJavaInstance = function() { + return java; +} +``` + +and then in the consuming class write: + +```javascript + +const javaInit = require('./javaInit'); +const java = javaInit.getJavaInstance(); + +//your code goes here +``` + + + ## Quick Examples ```javascript -var java = require("java"); -java.classpath.push("commons-lang3-3.1.jar"); +const java = require("java"); +java.classpath.push("commons-lang3-3.19.0.jar"); java.classpath.push("commons-io.jar"); -var list = java.newInstanceSync("java.util.ArrayList"); +const list1 = java.newInstanceSync("java.util.ArrayList"); +console.log(list1.sizeSync()); // 0 +list1.addSync('item1'); +console.log(list1.sizeSync()); // 1 -java.newInstance("java.util.ArrayList", function(err, list) { - list.addSync("item1"); - list.addSync("item2"); +java.newInstance("java.util.ArrayList", function(err, list2) { + list2.addSync("item1"); + list2.addSync("item2"); + console.log(list2.toStringSync()); // [item1, item2] }); -var ArrayList = java.import('java.util.ArrayList'); -var list = new ArrayList(); -list.addSync('item1'); +const ArrayList = java.import('java.util.ArrayList'); +const list3 = new ArrayList(); +list3.addSync('item1'); +list3.equalsSync(list1); // true +``` + +### Create a char array + +```javascript +const charArray = java.newArray("char", "hello world\n".split('')); +``` + +### Create a byte array + +```javascript +const byteArray = java.newArray( + "byte", + "hello world\n" + .split('') + .map(function(c) { return java.newByte(String.prototype.charCodeAt(c)); })); +``` + +### Using java.lang.Long and long + +JavaScript only supports 32-bit integers. Because of this java longs must be treated specially. +When getting a long result the value may be truncated. If you need the original value there is +a property off of the result called "longValue" which contains the un-truncated value as a string. +If you are calling a method that takes a long you must create it using [java.newInstance](#javaNewInstance). + +```javascript +const javaLong = java.newInstanceSync("java.lang.Long", 5); +console.log('Possibly truncated long value: ' + javaLong); +console.log('Original long value (as a string): ' + javaLong.longValue); +java.callStaticMethodSync("Test", "staticMethodThatTakesALong", javaLong); +``` + +### Exceptions + +Exceptions from calling methods either caught using JavaScript try/catch block or passed +to a callback as the first parameter may have a property named "cause" which has a reference +to the Java Exception object which caused the error. + +```javascript +try { + java.methodThatThrowsExceptionSync(); +} catch(ex) { + console.log(ex.cause.getMessageSync()); +} +``` + + + +### AsyncOptions: control over the generation of sync, async & promise method variants. + +As of release 0.4.5 it became possible to create async methods that return promises by setting the `asyncOptions` property of the java object. With release 0.4.7 this feature is extended to allow changing the suffix assigned for sync and async method variants, and to further configure this module to optionally omit generation of any of these variants. + +Example: + +```javascript +const java = require("java"); +java.asyncOptions = { + asyncSuffix: undefined, // Don't generate node-style methods taking callbacks + syncSuffix: "", // Sync methods use the base name(!!) + promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise. +}; +java.classpath.push("commons-lang3-3.19.0.jar"); +java.classpath.push("commons-io.jar"); + +java.import("java.util.ArrayList"); // see NOTE below + +java.newInstancePromise("java.util.ArrayList") + .then(function(list) { return list.addPromise("item1"); }) + .then(function(list) { return list.addPromise("item2"); }) + .catch(function(err) { /* handle error */ }); +``` + +#### NOTES: + +* If you want the defacto standard behavior, simply don't set java.asyncOptions. +* If you do provide asyncOptions, be aware that this module will not generate method variants of a given flavor if you don't provide a string value for the corresponding suffix (`asyncSuffix`, `syncSuffix`, `promiseSuffix`). In the example above, the application is configured to omit the method variants using node-style async callback functions. +* Either (but not both) `asyncSuffix` or `syncSuffix` can be the empty string. If you want the defacto standard behavior for no suffix on async methods, you must provide an empty string for `asyncSuffix`. +* We've tested promises with five Promises/A+ implementations. See `testHelpers.js` for more information. +* NOTE: Due to specifics of initialization order, the methods `java.newInstancePromise`, `java.callMethodPromise`, and `java.callStaticMethodPromise` are not available until the JVM has been created. You may need to call some other java method such as `java.import()` to finalize java initialization, or even better, the function `java.ensureJvm()`. + +##### Special note about the exported module functions `newInstance`, `callMethod`, and `callStaticMethod`. +These methods come in both async and sync variants. If you provide the `promiseSuffix` attributes in asyncOptions then you'll also get the Promises/A+ variant for these three functions. However, if you change the defacto conventions for the `syncSuffix` (i.e. 'Sync') and/or `asyncSuffix` (i.e. '') it will not affect the naming for these three functions. I.e. no matter what you specify in asyncOptions, the async variants are named `newInstance`, `callMethod`, and `callStaticMethod`, and the sync variants are named `newInstanceSync`, `callMethodSync`, and `callStaticMethodSync`. + +## Varargs support + +With v0.5.0 node-java now supports methods with variadic arguments (varargs). Prior to v0.5.0, a JavaScript call to a Java varargs method had to construct an array of the variadic arguments using `java.newArray()`. With v0.5.0 JavaScript applications can simply use the variadic style. + +In most cases it is still acceptable to use `java.newArray()`. But it is now possible to pass a plain JavaScript array, or use the variadic style. For example, consider these snippets from the unit test file `test/varargs-test.js`: + +``` + test.equal(Test.staticVarargsSync(5, 'a', 'b', 'c'), '5abc'); + test.equal(Test.staticVarargsSync(5, ['a', 'b', 'c']), '5abc'); + test.equal(Test.staticVarargsSync(5, java.newArray('java.lang.String', ['a', 'b', 'c'])), '5abc'); + ``` +Note that when passing a JavaScript array (e.g. `['a', 'b', 'c']`) for a varargs parameter, node-java must infer the Java type of the array. If all of the elements are of the same JavaScript primitive type (`string` in this example) then node-java will create a Java array of the corresponding type (e.g. `java.lang.String`). The Java types that node-java can infer are: `java.lang.String`, `java.lang.Boolean`, `java.lang.Integer`, `java.lang.Long`, and `java.lang.Double`. If an array has a mix of `Integer`, `Long`, and `Double`, then the inferred type will be `java.lang.Number`. Any other mix will result in an inferred type of `java.lang.Object`. + +Methods accepting varargs of a generic type are also problematic. You will need to fall back to using `java.newArray()`. See [Issue #285](https://github.com/joeferner/node-java/issues/285). + +## JVM Creation + +With v0.5.1 a new API is available to make it easier for a complex application to have full control over JVM creation. In particular, it is now easier to compose an application from several modules, each of which must add to the Java classpath and possibly do other operations just before or just after the JVM has been created. See the methods [ensureJvm](#javaEnsureJvm) and [registerClient](#javaRegisterClient). See also several of the tests in the testAsyncOptions directory. + +# Release Notes + +### v0.5.0 + +* Support for varargs. This change is not 100% backwards compatible, but the fix is generally easy and results in more natural code. + +### v0.2.0 + +* java.lang.Long and long primitives are handled better. See + \([Issue #37](https://github.com/nearinfinity/node-java/issues/37)\) and + \([Issue #40](https://github.com/nearinfinity/node-java/issues/40)\). + # Index ## java + * [classpath](#javaClasspath) + * [options](#javaOptions) + * [asyncOptions](#javaAsyncOptions) * [import](#javaImport) * [newInstance](#javaNewInstance) + * [instanceOf](#javaInstanceOf) * [callStaticMethod](#javaCallStaticMethod) + * [callMethod](#javaCallMethod) * [getStaticFieldValue](#javaGetStaticFieldValue) * [setStaticFieldValue](#javaSetStaticFieldValue) * [newArray](#javaNewArray) * [newByte](#javaNewByte) + * [newShort](#javaNewShort) + * [newLong](#javaNewLong) + * [newChar](#javaNewChar) + * [newDouble](#javaNewDouble) + * [newFloat](#javaNewFloat) * [newProxy](#javaNewProxy) + * [isJvmCreated](#javaIsJvmCreated) + * [registerClient](#javaRegisterClient) + * [registerClientP](#javaRegisterClientP) + * [ensureJvm](#javaEnsureJvm) ## java objects * [Call Method](#javaObjectCallMethod) @@ -67,119 +330,239 @@ list.addSync('item1'); # API Documentation - -## java + + +# java + + + +## classpath - -**java.import(className)** +*java.classpath** -Loads the class given by className such that it acts and feels like a javascript object. +Array of paths or jars to pass to the creation of the JVM. + +All items must be added to the classpath before calling any other node-java methods. + +__Example__ + + java.classpath.push('commons.io.jar'); + java.classpath.push('src'); + +## options + + + +*java.options** + +Array of options to pass to the creation of the JVM. + +All items must be added to the options before calling any other node-java methods. + +__Example__ + + java.options.push('-Djava.awt.headless=true'); + java.options.push('-Xmx1024m'); + +## asyncOptions + +```javascript +java.asyncOptions = { + asyncSuffix: undefined, // Don't generate node-style methods taking callbacks + syncSuffix: "", // Sync methods use the base name(!!) + promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise. + ifReadOnlySuffix: "_alt" +}; +``` + + * `asyncSuffix` Suffix for callback-based async method call signatures. + * `syncSuffix` Suffix for synchronous method call signatures. + * `promiseSuffix` Suffix for promise-based async method call signatures + * `ifReadOnlySuffix` See [Static Member Name Conflicts](#staticMemberNameConflicts). + +See [Async Options](#asyncOptionsDetails) for details. + +## import + + + +*java.import(className)** + +Loads the class given by className such that it acts and feels like a JavaScript object. __Arguments__ - * className - The name of the class to create. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) + * className - The name of the class to create. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). __Example__ - var Test = java.import('Test'); + const Test = java.import('Test'); Test.someStaticMethodSync(5); console.log(Test.someStaticField); - var test = new Test(); + const value1 = Test.NestedEnum.Value1; + + const test = new Test(); list.instanceMethodSync('item1'); - -**java.newInstance(className, [args...], callback)** +## newInstance + + + +*java.newInstance(className, [args...], callback)** **java.newInstanceSync(className, [args...]) : result** -Creates an instance of the specified class. If you are using the sync method an exception will be throw if an error occures, +Creates an instance of the specified class. If you are using the sync method an exception will be throw if an error occurs, otherwise it will be the first argument in the callback. __Arguments__ - * className - The name of the class to create. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) + * className - The name of the class to create. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). * callback(err, item) - Callback to be called when the class is created. __Example__ - var list = java.newInstanceSync("java.util.ArrayList"); + const list = java.newInstanceSync("java.util.ArrayList"); java.newInstance("java.util.ArrayList", function(err, list) { if(err) { console.error(err); return; } // new list }); - -**java.callStaticMethod(className, methodName, [args...], callback)** +## instanceOf + + + +*java.instanceOf(javaObject, className)** + +Determines of a javaObject is an instance of a class. + +__Arguments__ + + * javaObject - Instance of a java object returned from a method or from newInstance. + * className - A string class name. + +__Example__ + + const obj = java.newInstanceSync("my.package.SubClass"); + + if(java.instanceOf(obj, "my.package.SuperClass")) { + console.log("obj is an instance of SuperClass"); + } + +## callStaticMethod + + + +*java.callStaticMethod(className, methodName, [args...], callback)** **java.callStaticMethodSync(className, methodName, [args...]) : result** -Calls a static method on the specified class. If you are using the sync method an exception will be throw if an error occures, -otherwise it will be the first argument in the callback. +Calls a static method on the specified class. If you are using the sync method an exception will be throw if an error occurs, otherwise it will be the first argument in the callback. __Arguments__ - * className - The name of the class to call the method on. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) - * methodName - The name of the method to call. + * className - The name of the class to call the method on. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * methodName - The name of the method to call. The method name can include the full signature (see [Getting the full method signature](#getFullMethodSignature)). * callback(err, item) - Callback to be called when the class is created. __Example__ - var result = java.callStaticMethodSync("com.nearinfinty.MyClass", "doSomething", 42, "test"); + const result = java.callStaticMethodSync("com.nearinfinty.MyClass", "doSomething", 42, "test"); java.callStaticMethod("com.nearinfinty.MyClass", "doSomething", 42, "test", function(err, results) { if(err) { console.error(err); return; } // results from doSomething }); - -**java.getStaticFieldValue(className, fieldName)** +## callMethod + + + +*java.callMethod(instance, methodName, [args...], callback)** + +**java.callMethodSync(instance, methodName, [args...]) : result** + +Calls a method on the specified instance. If you are using the sync method an exception will be throw if an error occurs, +otherwise it will be the first argument in the callback. + +__Arguments__ + + * instance - An instance of the class from newInstance. + * methodName - The name of the method to call. The method name can include the full signature (see [Getting the full method signature](#getFullMethodSignature)). + * callback(err, item) - Callback to be called when the class is created. + +__Example__ + + const instance = java.newInstanceSync("com.nearinfinty.MyClass"); + + const result = java.callMethodSync("com.nearinfinty.MyClass", "doSomething", 42, "test"); + + java.callMethod(instance, "doSomething", 42, "test", function(err, results) { + if(err) { console.error(err); return; } + // results from doSomething + }); + +## getStaticFieldValue + + + +*java.getStaticFieldValue(className, fieldName)** Gets a static field value from the specified class. __Arguments__ - * className - The name of the class to get the value from. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) + * className - The name of the class to get the value from. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). * fieldName - The name of the field to get the value from. __Example__ - var data = java.getStaticFieldValue("com.nearinfinty.MyClass", "data"); + const data = java.getStaticFieldValue("com.nearinfinty.MyClass", "data"); - -**java.setStaticFieldValue(className, fieldName, newValue)** +## setStaticFieldValue + + + +*java.setStaticFieldValue(className, fieldName, newValue)** Sets a static field value on the specified class. __Arguments__ - * className - The name of the class to set the value on. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) + * className - The name of the class to set the value on. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). * fieldName - The name of the field to set the value on. * newValue - The new value to assign to the field. __Example__ - java.getStaticFieldValue("com.nearinfinty.MyClass", "data", "Hello World"); + java.setStaticFieldValue("com.nearinfinty.MyClass", "data", "Hello World"); + +## newArray - -**java.newArray(className, values[])** + -Creates a new java array of type class. +*java.newArray(className, values[])** + +Creates a new java array of given glass type. To create array of primitive types like `char`, `byte`, etc, pass the primitive type name (eg. `java.newArray("char", "hello world\n".split(''))`). __Arguments__ - * className - The name of the type of array elements. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) - * values - A javascript array of values to assign to the java array. + * className - The name of the type of array elements. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * values - A JavaScript array of values to assign to the java array. __Example__ - var newArray = java.newArray("java.lang.String", ["item1", "item2", "item3"]); + const newArray = java.newArray("java.lang.String", ["item1", "item2", "item3"]); + +## newByte - -**java.newByte(val)** + -Creates a new java byte. This is needed because javascript does not have the concept of a byte. +*java.newByte(val)** + +Creates a new java byte. This is needed because JavaScript does not have the concept of a byte. __Arguments__ @@ -187,38 +570,159 @@ __Arguments__ __Example__ - var b = java.newByte(12); + const b = java.newByte(12); + +## newShort + + + +*java.newShort(val)** + +Creates a new java short. This is needed because JavaScript does not have the concept of a short. + +__Arguments__ + + * val - The value of the java short. + +__Example__ + + const s = java.newShort(12); + +## newLong + + - -**java.newProxy(interfaceName, functions)** +*java.newLong(val)** + +Creates a new java long. This is needed because JavaScript does not have the concept of a long. + +__Arguments__ + + * val - The value of the java long. + +__Example__ + + const s = java.newLong(12); + +## newChar + + + +*java.newChar(val)** + +Creates a new java char. This is needed because JavaScript does not have the concept of a char. + +__Arguments__ + + * val - The value of the java char. + +__Example__ + + const ch = java.newChar('a'); + +## newDouble + + + +*java.newDouble(val)** + +Creates a new java double. This is needed to force JavaScript's number to a double to call some methods. + +__Arguments__ + + * val - The value of the java double. + +__Example__ + + const d = java.newDouble(3.14); + +## newFloat + + + +*java.newFloat(val)** + +Creates a new java float. This is needed to force JavaScript's number to a float to call some methods. + +__Arguments__ + + * val - The value of the java float. + +__Example__ + + const f = java.newFloat(3.14); + +## newProxy + + + +*java.newProxy(interfaceName, functions)** Creates a new java Proxy for the given interface. Functions passed in will run on the v8 main thread and not a new thread. -The returned object has two methods ref() and unref() which you can use to maintain references to prevent premature -garbage collection. You must call these methods to ensure the proxy stays around. +The returned object has a method unref() which you can use to free the object for +garbage collection. __Arguments__ - * interfaceName - The name of the interface to proxy. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass) + * interfaceName - The name of the interface to proxy. Separate nested classes using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). * functions - A hash of functions matching the function in the interface. __Example__ - var myProxy = java.newProxy('java.lang.Runnable', { + const myProxy = java.newProxy('java.lang.Runnable', { run: function () { // This is actually run on the v8 thread and not the new java thread console.log("hello from thread"); } }); - var thread = java.newInstanceSync("java.lang.Thread", myProxy); + const thread = java.newInstanceSync("java.lang.Thread", myProxy); thread.start(); - -## java object +## isJvmCreated + + - -**obj._methodName_([args...], callback)** +*java.isJvmCreated()** + +Returns true if the JVM has been created. The JVM can only be created once. + +## registerClient + + + +*java.registerClient(before, after)** + +Register that a client wants to be called back immediately before and/or immediately after the JVM is created. If used, this function must be called before the JVM has been created. The before function is typically used to add to the classpath. The function may execute asynchronous operations (such as a async glob function). The after function is sometimes useful for doing one-time initialization that requires the JVM to first be initialized. If either function is unnecessary, use `null` or `undefined`. See also `registerClientP` and `ensureJvm`. See the unit tests in `testAsyncOptions` for examples. + +## registerClientP + + + +*java.registerClientP(before, after)** + +Like java.registerClient, but before and after are assumed to be functions returning promises. + +## ensureJvm + + + +*java.ensureJvm(callback)** + +If the JVM has not yet been created, execute the full JVM initialization process, then call callback function when initialization is complete. If the JVM has been created, just call the callback. Note that the full initialization process includes: 1) executing all registered client *before* hooks, 2) creating the JVM, then 3) executing all registered client *after* hooks. + + + + + +# `java` object + +## Call Method + + + +*obj._methodName_([args...], callback)** **obj._methodNameSync_([args...]) : result** @@ -232,14 +736,17 @@ __Arguments__ __Example__ - var list = java.newInstanceSync("java.util.ArrayList"); + const list = java.newInstanceSync("java.util.ArrayList"); list.addSync("item1"); list.add("item2", function(err, result) { if(err) { console.error(err); return; } }); - -**obj._fieldName_ = val** +## Field Access + + + +*obj._fieldName_ = val** **val = obj._fieldName_** @@ -248,9 +755,137 @@ field values. __Example__ - var list = java.newInstanceSync("com.nearinfinty.MyClass"); + const list = java.newInstanceSync("com.nearinfinty.MyClass"); list.data = "test"; - var data = list.data; + const data = list.data; + +## Getting the Full Method Signature + + + +Run `javap -s -classpath `. Find the method name you are looking for. For example: + +``` +public int methodAmbiguous(java.lang.Double); + Signature: (Ljava/lang/Double;)I +``` + +The full method signature would be `methodAmbiguous(Ljava/lang/Double;)I`. + +If you have grep, a shortcut is `javap -s -classpath . my.company.MyClass | grep -A1 myMethodName`. + +# Signal Handling + +The JVM intercepts signals (Ctrl+C, etc.) before node/v8 gets to handle them. To fix this there are a couple options. + +## Signal Handling Option 1 + +One option to capture these events is to add the following flag: + +```javascript +java.options.push('-Xrs'); +``` + +As `man java` says, the `-Xrs` flag will “reduce usage of operating-system signals by [the] Java virtual machine (JVM)”, to avoid issues when developing “applications that embed the JVM”. + +## Signal Handling Option 2 + +Hook into the runtime shutdown hook. + +First create a java wrapper around the Runtime.addShutdownHook method to allow using a proxy object. + +```java +public class ShutdownHookHelper { + public static void setShutdownHook(final Runnable r) { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + r.run(); + } + }); + } +} +``` + +Compile ShutdownHookHelper and then use it as follows. + +```javascript +const java = require('./'); +java.classpath.push('.'); +const ShutdownHookHelper = java.import('ShutdownHookHelper'); + +ShutdownHookHelper.setShutdownHookSync(java.newProxy('java.lang.Runnable', { + run: function () { + console.log("do shutdown stuff here instead."); + } +})); +``` + +# Object lifetime + +When you call a Java method through node-java, any arguments (V8/JavaScript objects) will be converted to Java objects on the v8 main thread via a call to v8ToJava (found in utils.cpp). The JavaScript object is not held on to and can be garbage collected by v8. If this is an async call, the reference count on the Java objects will be incremented. The Java method will be invoked in a node.js async thread (see uv_queue_work). When the method returns, the resulting object will be returned to the main v8 thread and converted to JavaScript objects via a call to javaToV8 and the Java object's reference count will then be decremented to allow for garbage collection. The resulting v8 object will then be returned to the callers callback function. + + + + +# Static member name conflicts ('name', 'arguments', 'caller') + +The JavaScript object returned by `java.import(classname)` is a JavaScript constructor Function, implemented such that you can create instances of the Java class. For example: + +```javascript +const Test = java.import('Test'); +const test = new Test(); + +Test.someStaticMethod(function(err, result) { ... }); + +const value1 = Test.NestedEnum.Value1; +``` + +But JavaScript reserves a few property names of Function objects: `name`, `arguments`, and `caller`. If your class has public static members (either methods or fields) with these names, node-java is unable to create the necessary property to implement the class's API. For example, suppose your class `Test` implements a static method named `caller`, or has a `NestedEnum` with a value `name`: + +```java +public class Test { + ... + public static String caller() { return "something"; } + public enum NestedEnum { foo, name }; +} +``` + +In JavaScript, you would expect to be able to use those static members like this: + +```javascript +const Test = java.import('Test'); +Test.caller(function(err, result) { ... }); // ERROR +const value = Test.NestedEnum.name; // ERROR +``` + +Node-java can't create those properties, so the above code won't work. Instead, node-java appends a suffix to the name. The default suffix is simply an underscore `_`, but you can change the suffix using `asyncOptions`: + +```javascript +const java = require('java'); + +java.asyncOptions = { + asyncSuffix: "", + syncSuffix: "Sync", + ifReadOnlySuffix: "_alt" +}; + +const Test = java.import('Test'); +Test.caller_alt(function(err, result) { ... }); // OK +const value = Test.NestedEnum.name_alt; // OK +``` + +# Troubleshooting + +## Error: Cannot find module '../build/jvm_dll_path.json' + +Either `./scripts/postInstall.js` didn't run or there was a problem detecting java. Try running `./scripts/postInstall.js` manually. + +## Debugging + + npm install + npx node-gyp build --debug + gdb --args "$(which node)" ./node_modules/.bin/vitest test ## License diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 00000000..25fa488c --- /dev/null +++ b/binding.gyp @@ -0,0 +1,142 @@ +{ + 'variables': { + 'arch%': 'amd64', # linux JVM architecture. See $(JAVA_HOME)/jre/lib/<@(arch)/server/ + 'uname_m': '', + 'conditions': [ + ['target_arch=="ia32"', { + 'arch%': 'i386' + }], + ['OS!="win"', { + 'uname_m': '=8.0.0" + } + }, + "node_modules/@types/find-root": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/find-root/-/find-root-1.1.4.tgz", + "integrity": "sha512-2EXK/+gVhVgtt4JqThbEncORvpYJKzi9tQGmI3EkU2jTgMzQsrPm/hbd5xe5uPdeFzIW5gh2PRvvPjaUsI8vpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/java": { + "resolved": "../..", + "link": true + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/examples/lucene/package.json b/examples/lucene/package.json new file mode 100644 index 00000000..b436b24c --- /dev/null +++ b/examples/lucene/package.json @@ -0,0 +1,17 @@ +{ + "name": "java-example-lucene", + "private": true, + "scripts": { + "build": "tsc --build", + "build:watch": "tsc --build --watch", + "start": "node build/example.js" + }, + "dependencies": { + "find-root": "^1.1.0", + "java": "file:../.." + }, + "devDependencies": { + "@types/find-root": "^1.1.4", + "typescript": "^5.8.3" + } +} diff --git a/examples/lucene/tsconfig.json b/examples/lucene/tsconfig.json new file mode 100644 index 00000000..53c363e6 --- /dev/null +++ b/examples/lucene/tsconfig.json @@ -0,0 +1,113 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./build", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": false /* Skip type checking all .d.ts files. */ + } +} diff --git a/examples/mixJavaAndNode/runMyClass.js b/examples/mixJavaAndNode/runMyClass.js new file mode 100755 index 00000000..8c285c94 --- /dev/null +++ b/examples/mixJavaAndNode/runMyClass.js @@ -0,0 +1,9 @@ +#!/usr/bin/env node + +const java = require("../../"); +java.classpath.push("./src"); + +const MyClass = java.import("com.nearinfinity.nodeJava.MyClass"); + +const result = MyClass.addNumbersSync(1, 2); +console.log(result); diff --git a/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.class b/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.class new file mode 100644 index 00000000..59b3deb5 Binary files /dev/null and b/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.class differ diff --git a/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.java b/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.java new file mode 100644 index 00000000..e3480038 --- /dev/null +++ b/examples/mixJavaAndNode/src/com/nearinfinity/nodeJava/MyClass.java @@ -0,0 +1,7 @@ +package com.nearinfinity.nodeJava; + +public class MyClass { + public static int addNumbers(int a, int b) { + return a + b; + } +} diff --git a/index.js b/index.js index f9e0d85a..73442826 100644 --- a/index.js +++ b/index.js @@ -1,2 +1 @@ - -module.exports = require("./lib/nodeJavaBridge"); +module.exports = require("./src-node/nodeJavaBridge"); diff --git a/jarjar.rule b/jarjar.rule deleted file mode 100644 index 35923389..00000000 --- a/jarjar.rule +++ /dev/null @@ -1 +0,0 @@ -rule org.apache.commons.lang3.** com.nearinfinity.org.apache.commons.lang3.@1 diff --git a/java.d.ts b/java.d.ts new file mode 100644 index 00000000..c2248377 --- /dev/null +++ b/java.d.ts @@ -0,0 +1,343 @@ +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare const NodeJavaCore: NodeJavaCore.Java; +export = NodeJavaCore; + +declare namespace NodeJavaCore { + type JavaObject = any; + type JavaError = Error & { cause?: JavaObject }; + + export interface Callback { + (err?: Error, result?: T): void; + } + + export interface JavaCallback { + (err?: JavaError, result?: T): void; + } + + interface AsyncOptions { + /** + * Suffix for synchronous method call signatures. + */ + syncSuffix: string; + + /** + * Suffix for callback-based async method call signatures. + */ + asyncSuffix?: string | undefined; + + /** + * Suffix for promise-based async method call signatures + */ + promiseSuffix?: string | undefined; + + /** + * The JavaScript object returned by `java.import(classname)` is a JavaScript constructor + * Function, implemented such that you can create instances of the Java class. For example: + */ + ifReadOnlySuffix?: string | undefined; + } + + interface ProxyFunctions { + [index: string]: Function; + } + + interface Java { + /** + * Array of paths or jars to pass to the creation of the JVM. + * + * All items must be added to the classpath before calling any other node-java methods. + * + * @example + * java.classpath.push('commons.io.jar'); + * java.classpath.push('src'); + */ + classpath: string[]; + + /** + * Array of options to pass to the creation of the JVM. + * + * All items must be added to the options before calling any other node-java methods. + * + * @example + * java.options.push('-Djava.awt.headless=true'); + * java.options.push('-Xmx1024m'); + */ + options: string[]; + + /** + * @see AsyncOptions + */ + asyncOptions: AsyncOptions; + + /** + * Location of nodejavabridge_bindings.node + */ + nativeBindingLocation: string; + + /** + * Calls a method on the specified instance. If you are using the sync method an exception + * will be throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param instance An instance of the class from newInstance. + * @param methodName The name of the method to call. The method name can include the full + * signature (see [Getting the full method signature](README.md#getFullMethodSignature)). + * @param args The arguments to pass to the method, the last argument will be the callback to the function + * + * @example + * const instance = java.newInstanceSync("com.nearinfinty.MyClass"); + * java.callMethod(instance, "doSomething", 42, "test", function(err, results) { + * if(err) { console.error(err); return; } + * // results from doSomething + * }); + */ + callMethod(instance: JavaObject, methodName: string, ...args: any[]): void; + + /** + * Calls a method on the specified instance. If you are using the sync method an exception + * will be throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param instance An instance of the class from newInstance. + * @param methodName The name of the method to call. The method name can include the full + * signature (see [Getting the full method signature](README.md#getFullMethodSignature)). + * @param args The arguments to pass to the method + * @returns The result of the method call + * + * @example + * const instance = java.newInstanceSync("com.nearinfinty.MyClass"); + * const result = java.callMethodSync("com.nearinfinty.MyClass", "doSomething", 42, "test"); + */ + callMethodSync(instance: JavaObject, methodName: string, ...args: any[]): any; + + /** + * Calls a static method on the specified class. If you are using the sync method an exception will be + * throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param className The name of the class to call the method on. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param methodName The name of the method to call. The method name can include the full + * signature (see [Getting the full method signature](README.md#getFullMethodSignature)). + * @param args The arguments to pass to the method, the last argument will be the callback to the function + */ + callStaticMethod(className: string, methodName: string, ...args: any[]): void; + + /** + * Calls a static method on the specified class. If you are using the sync method an exception will be + * throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param className The name of the class to call the method on. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param methodName The name of the method to call. The method name can include the full + * signature (see [Getting the full method signature](README.md#getFullMethodSignature)). + * @param args The arguments to pass to the method + * @returns The result of the method call + */ + callStaticMethodSync(className: string, methodName: string, ...args: any[]): any; + + /** + * Finds the class with the specified binary name. This method should be overridden by class loader + * implementations that follow the delegation model for loading classes, and will be invoked by the + * loadClass method after checking the parent class loader for the requested class. The default + * implementation throws a ClassNotFoundException. + * + * @param className The binary name of the class + * @returns The resulting Class object + */ + findClassSync(className: string): JavaObject; + + /** + * Gets a static field value from the specified class. + * + * @param className The name of the class to get the value from. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param fieldName The name of the field to get the value from. + * @returns The valid of the static field + */ + getStaticFieldValue(className: string, fieldName: string): any; + + /** + * Sets a static field value on the specified class. + * + * @param className The name of the class to set the value on. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param fieldName The name of the field to set the value on. + * @param newValue The new value to assign to the field. + */ + setStaticFieldValue(className: string, fieldName: string, newValue: any): void; + + /** + * Determines of a javaObject is an instance of a class. + * + * @param javaObject Instance of a java object returned from a method or from newInstance. + * @param className A string class name. + * + * @example + * const obj = java.newInstanceSync("my.package.SubClass"); + * if(java.instanceOf(obj, "my.package.SuperClass")) { + * console.log("obj is an instance of SuperClass"); + * } + */ + instanceOf(javaObject: JavaObject, className: string): boolean; + + /** + * Register that a client wants to be called back immediately before and/or immediately + * after the JVM is created. If used, this function must be called before the JVM has been + * created. The before function is typically used to add to the classpath. The function may + * execute asynchronous operations (such as a async glob function). The after function is + * sometimes useful for doing one-time initialization that requires the JVM to first be + * initialized. If either function is unnecessary, use `null` or `undefined`. See also + * `registerClientP` and `ensureJvm`. See the unit tests in `testAsyncOptions` for examples. + */ + registerClient( + before: ((cb: Callback) => void) | undefined | null, + after?: (cb: Callback) => void + ): void; + + /** + * Register that a client wants to be called back immediately before and/or immediately + * after the JVM is created. If used, this function must be called before the JVM has been + * created. The before function is typically used to add to the classpath. The function may + * execute asynchronous operations (such as a async glob function). The after function is + * sometimes useful for doing one-time initialization that requires the JVM to first be + * initialized. If either function is unnecessary, use `null` or `undefined`. See also + * `registerClientP` and `ensureJvm`. See the unit tests in `testAsyncOptions` for examples. + */ + registerClientP(beforeP: (() => Promise) | undefined | null, afterP?: () => Promise): void; + + /** + * If the JVM has not yet been created, execute the full JVM initialization process, then + * call callback function when initialization is complete. If the JVM has been created, just + * call the callback. Note that the full initialization process includes: 1) executing all + * registered client *before* hooks, 2) creating the JVM, then 3) executing all registered + * client *after* hooks. + */ + ensureJvm(done: Callback): void; + + /** + * If the JVM has not yet been created, execute the full JVM initialization process, then + * call callback function when initialization is complete. If the JVM has been created, just + * call the callback. Note that the full initialization process includes: 1) executing all + * registered client *before* hooks, 2) creating the JVM, then 3) executing all registered + * client *after* hooks. + */ + ensureJvm(): Promise; + + /** + * Returns true if the JVM has been created. The JVM can only be created once. + */ + isJvmCreated(): boolean; + + /** + * Creates a new java byte. This is needed because JavaScript does not have the concept of a byte. + */ + newByte(val: number): JavaObject; + + /** + * Creates a new java short. This is needed because JavaScript does not have the concept of a short. + */ + newShort(val: number): JavaObject; + + /** + * Creates a new java long. This is needed because JavaScript does not have the concept of a long. + */ + newLong(val: number): JavaObject; + + /** + * Creates a new java char. This is needed because JavaScript does not have the concept of a char. + */ + newChar(val: string | number): JavaObject; + + /** + * Creates a new java float. This is needed to force JavaScript's number to a float to call some methods. + */ + newFloat(val: number): JavaObject; + + /** + * Creates a new java double. This is needed to force JavaScript's number to a double to call some methods. + */ + newDouble(val: number): JavaObject; + + /** + * Loads the class given by className such that it acts and feels like a JavaScript object. + * + * @param className The name of the class to create. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * + * @example + * const Test = java.import('Test'); + * Test.someStaticMethodSync(5); + * console.log(Test.someStaticField); + * + * const value1 = Test.NestedEnum.Value1; + * + * const test = new Test(); + * list.instanceMethodSync('item1'); + */ + import(className: string): JavaObject; + + /** + * Creates an instance of the specified class. If you are using the sync method an exception will + * be throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param className The name of the class to create. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param args Arguments to pass to the constructor, the last argument is a callback function + */ + newInstance(className: string, ...args: any[]): void; + + /** + * Creates an instance of the specified class. If you are using the sync method an exception will + * be throw if an error occurs, otherwise it will be the first argument in the callback. + * + * @param className The name of the class to create. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param args Arguments to pass to the constructor + */ + newInstanceSync(className: string, ...args: any[]): JavaObject; + + /** + * Creates a new java array of given glass type. To create array of primitive types + * like `char`, `byte`, etc, pass the primitive typename + * (eg. `java.newArray("char", "hello world\n".split(''))`). + * + * @param className The name of the type of array elements. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param arg A JavaScript array of values to assign to the java array. + */ + newArray(className: string, arg: any[]): JavaObject; + + /** + * Get the current class loader + */ + getClassLoader(): JavaObject; + + /** + * Creates a new java Proxy for the given interface. Functions passed in will run on the v8 + * main thread and not a new thread. + * + * The returned object has a method unref() which you can use to free the object for garbage + * collection. + * + * @param interfaceName The name of the interface to proxy. Separate nested classes + * using `'$'` (eg. `com.nearinfinty.MyClass$NestedClass`). + * @param functions A hash of functions matching the function in the interface. + * + * @example + * const myProxy = java.newProxy('java.lang.Runnable', { + * run: function () { + * // This is actually run on the v8 thread and not the new java thread + * console.log("hello from thread"); + * } + * }); + * + * const thread = java.newInstanceSync("java.lang.Thread", myProxy); + * thread.start(); + */ + newProxy(interfaceName: string, functions: ProxyFunctions): JavaObject; + + /** + * Stops the running event loop + */ + stop(): void; + } +} diff --git a/lib/nodeJavaBridge.js b/lib/nodeJavaBridge.js deleted file mode 100644 index b98aaae7..00000000 --- a/lib/nodeJavaBridge.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -var path = require('path'); -var binaryPath = path.resolve(path.join(__dirname, "../build/Release/nodejavabridge_bindings.node")); -var bindings = require(binaryPath); - -var java = module.exports = new bindings.Java(); -java.classpath.push(__dirname + "/../commons-lang3-node-java.jar"); -java.classpath.push(__dirname + "/../src-java"); -java.nativeBindingLocation = binaryPath; - -var MODIFIER_PUBLIC = 1; -var MODIFIER_STATIC = 8; - -java.import = function (name) { - var clazz = java.findClassSync(name); // TODO: change to Class.forName when classloader issue is resolved. - var result = function () { - var args = [name]; - for (var i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - return java.newInstanceSync.apply(java, args); - }; - var i; - - // copy static fields - var fields = clazz.getDeclaredFieldsSync(); - for (i = 0; i < fields.length; i++) { - if (((fields[i].getModifiersSync() & MODIFIER_PUBLIC) === MODIFIER_PUBLIC) - && ((fields[i].getModifiersSync() & MODIFIER_STATIC) === MODIFIER_STATIC)) { - var fieldName = fields[i].getNameSync(); - result.__defineGetter__(fieldName, function (name, fieldName) { - return java.getStaticFieldValue(name, fieldName); - }.bind(this, name, fieldName)); - result.__defineSetter__(fieldName, function (name, fieldName, val) { - java.setStaticFieldValue(name, fieldName, val); - }.bind(this, name, fieldName)); - } - } - - // copy static methods - var methods = clazz.getDeclaredMethodsSync(); - for (i = 0; i < methods.length; i++) { - if (((methods[i].getModifiersSync() & MODIFIER_PUBLIC) === MODIFIER_PUBLIC) - && ((methods[i].getModifiersSync() & MODIFIER_STATIC) === MODIFIER_STATIC)) { - var methodName = methods[i].getNameSync(); - result[methodName + 'Sync'] = java.callStaticMethodSync.bind(java, name, methodName); - result[methodName] = java.callStaticMethod.bind(java, name, methodName); - } - } - - return result; -}; diff --git a/mnm.js b/mnm.js deleted file mode 100755 index b19d30f3..00000000 --- a/mnm.js +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env node - -var path = require('path'); -var fs = require('fs'); -var Builder = require('mnm'); -var builder = new Builder(); - -var existsSync = fs.existsSync || path.existsSync; - -builder.appendUnique('CXXFLAGS', ['-Isrc/']); -builder.appendUnique('CXXFLAGS', ['-DHAVE_CONFIG_H']); - -// MAC has a built in JVM -if (existsSync("/System/Library/Frameworks/JavaVM.framework/")) { - var jdkIncludeDir = process.env["JDK_INCLUDE_DIR"] || "/System/Library/Frameworks/JavaVM.framework/Headers"; - builder.appendUnique('CXXFLAGS', '-I' + jdkIncludeDir); - builder.appendUnique('LINKFLAGS', ['-framework', 'JavaVM']); -} else { - var javaHome = builder.trimQuotes(process.env["JAVA_HOME"]); - - // JDK Include directory - var jdkIncludeDir = process.env["JDK_INCLUDE_DIR"]; - if (!javaHome && !jdkIncludeDir) { - builder.fail("You must set JAVA_HOME or JDK_INCLUDE_DIR environment variable"); - } - jdkIncludeDir = jdkIncludeDir || path.join(javaHome, "include"); - builder.failIfNotExists(jdkIncludeDir, 'Could not find "%s" check JAVA_HOME or JDK_INCLUDE_DIR environment variable.'); - builder.appendUnique('CXXFLAGS', '-I' + jdkIncludeDir); - - // JDK additional include directory - var jdkAdditionalIncludeDirGuess; - if (process.platform == 'win32') { - jdkAdditionalIncludeDirGuess = path.join(jdkIncludeDir, "win32"); - } else { - jdkAdditionalIncludeDirGuess = path.join(jdkIncludeDir, "linux"); - } - var jdkAdditionalIncludeDir = process.env["JDK_AUX_INCLUDE_DIR"] || jdkAdditionalIncludeDirGuess; - builder.failIfNotExists(jdkAdditionalIncludeDir, 'Could not find "%s" check JAVA_HOME or JDK_AUX_INCLUDE_DIR environment variable.'); - builder.appendUnique('CXXFLAGS', '-I' + jdkAdditionalIncludeDir); - - // JDK lib directory - if (process.platform == 'win32') { - var jdkLibDir = process.env["JDK_LIB_DIR"] || path.join(javaHome, "lib"); - if (!jdkLibDir) { - builder.fail("You must set JAVA_HOME or JDK_LIB_DIR environment variable"); - } - builder.appendLinkerSearchDir(jdkLibDir); - } else { - var jdkLibDirGuess = null; - if (javaHome) { - if (existsSync(path.join(javaHome, "/jre/lib/i386/server/"))) { - jdkLibDirGuess = path.join(javaHome, "/jre/lib/i386/server/"); - } else { - jdkLibDirGuess = path.join(javaHome, "/jre/lib/amd64/server/"); - } - } - var jdkLibDir = process.env["JDK_LIB_DIR"]; - if (!jdkLibDirGuess && !jdkLibDir) { - builder.fail("You must set JAVA_HOME or JDK_LIB_DIR environment variable"); - } - jdkLibDir = jdkLibDir || jdkLibDirGuess; - builder.failIfNotExists(jdkLibDir, 'Could not find "%s" check JAVA_HOME or JDK_LIB_DIR environment variable.'); - builder.appendLinkerSearchDir(jdkLibDir); - builder.appendUnique('LINKFLAGS', '-Wl,-rpath,' + jdkLibDir); - } - - builder.appendLinkerLibrary('jvm'); -} - -builder.target = "nodejavabridge_bindings"; -builder.appendSourceDir('./src'); -builder.appendUnique('CXXFLAGS', '-Isrc/'); - -builder.run(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..fb5faeba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3589 @@ +{ + "name": "java", + "version": "0.18.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "java", + "version": "0.18.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "find-java-home": "^2.0.0", + "glob": "^11.0.3", + "nan": "^2.23.0", + "node-gyp": "^11.5.0" + }, + "devDependencies": { + "@eslint/js": "^9.37.0", + "@types/find-root": "^1.1.4", + "@types/node": "^24.8.0", + "chalk": "^5.6.2", + "eslint": "^9.37.0", + "find-root": "^1.1.0", + "globals": "^16.4.0", + "prettier": "^3.6.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.1", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/find-root": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/find-root/-/find-root-1.1.4.tgz", + "integrity": "sha512-2EXK/+gVhVgtt4JqThbEncORvpYJKzi9tQGmI3EkU2jTgMzQsrPm/hbd5xe5uPdeFzIW5gh2PRvvPjaUsI8vpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.0.tgz", + "integrity": "sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", + "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz", + "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz", + "integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz", + "integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-java-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-java-home/-/find-java-home-2.0.0.tgz", + "integrity": "sha512-m4Cf5WM5Y9UupofsLgcJuY5oFXVfVnfHx43pg3HJoVdtY2PN4Wfs7ex9svf7W7eLTP+6wmyBToaqGOCffHBHVA==", + "dependencies": { + "which": "~1.0.5", + "winreg": "~1.2.2" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz", + "integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.1", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "bin": { + "which": "bin/which" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winreg": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", + "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index f1b02e26..df60ce65 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,61 @@ { "name": "java", "description": "Bridge API to connect with existing Java APIs.", - "author": "Joe Ferner ", - "keywords": ["java", "jvm", "bridge"], - "version": "0.0.5", - "engines": { "node" : ">=0.6.0" }, + "author": "Joe Ferner ", + "keywords": [ + "java", + "jvm", + "bridge" + ], + "version": "0.18.0", + "engines": { + "node": ">=8.0.0" + }, "maintainers": [ - { "name": "Jeff Kunkle", "email": "jeff.kunkle@nearinfinity.com" }, - { "name": "Joe Ferner", "email": "joe.ferner@nearinfinity.com" } + { + "name": "Joe Ferner", + "email": "joe@fernsroth.com" + } ], - "bugs": { "url": "https://github.com/nearinfinity/node-java/issues" }, + "bugs": { + "url": "https://github.com/joeferner/node-java/issues" + }, "license": "MIT", - "repository": { "type": "git", "url": "https://github.com/nearinfinity/node-java.git" }, + "repository": { + "type": "git", + "url": "https://github.com/joeferner/node-java.git" + }, "dependencies": { - "mnm": "~0.0.3" + "find-java-home": "^2.0.0", + "glob": "^11.0.3", + "nan": "^2.23.0", + "node-gyp": "^11.5.0" }, "devDependencies": { - "nodeunit": "~0.6.4" + "@eslint/js": "^9.37.0", + "@types/find-root": "^1.1.4", + "@types/node": "^24.8.0", + "chalk": "^5.6.2", + "eslint": "^9.37.0", + "find-root": "^1.1.0", + "globals": "^16.4.0", + "prettier": "^3.6.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.1", + "vitest": "^3.2.4" }, "scripts": { - "test": "nodeunit test", - "install": "node mnm.js build" + "install": "node-gyp rebuild", + "clean": "rm -rf build", + "test": "tsc && node ./build/ts/scripts/testRunner.js", + "postinstall": "node ./scripts/postInstall.js", + "lint": "eslint --ext js,ts,tsx --report-unused-disable-directives --max-warnings 0 .", + "format": "prettier --write .", + "format-cpp": "clang-format --version; find src-cpp/ -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i", + "build-ts": "tsc", + "build-ts:watch": "tsc --watch", + "precommit": "npm run format-cpp && npm run format && npm run lint && npm run build-ts && npm test" }, - "main": "./index.js" + "main": "./index.js", + "types": "./java.d.ts" } diff --git a/scripts/commons-lang/.gitignore b/scripts/commons-lang/.gitignore new file mode 100644 index 00000000..8b8c81d7 --- /dev/null +++ b/scripts/commons-lang/.gitignore @@ -0,0 +1,2 @@ +target/ +dependency-reduced-pom.xml diff --git a/scripts/commons-lang/pom.xml b/scripts/commons-lang/pom.xml new file mode 100644 index 00000000..11c3470b --- /dev/null +++ b/scripts/commons-lang/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + commons-lang + commons-lang + 1 + + + + org.apache.commons + commons-lang3 + 3.19.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + org.apache + nodejava.org.apache + + + + + + + + + \ No newline at end of file diff --git a/scripts/compile-java.sh b/scripts/compile-java.sh new file mode 100755 index 00000000..ba590fe8 --- /dev/null +++ b/scripts/compile-java.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(dirname "$0") +cd "${SCRIPT_DIR}/.." + +if [ -z "${JAVA_HOME:-}" ] && [ -e "/usr/libexec/java_home" ]; then + export JAVA_HOME=$(/usr/libexec/java_home -v 11) +fi + +echo "Using $(javac -version)." +set -x + +javac -source 1.8 -target 1.8 src-java/node/*.java +javac -source 1.8 -target 1.8 test/*.java +javac -classpath src-java -h ./src-cpp src-java/node/NodeDynamicProxyClass.java +echo "complete!" diff --git a/scripts/findJavaHome.js b/scripts/findJavaHome.js new file mode 100644 index 00000000..53ffa337 --- /dev/null +++ b/scripts/findJavaHome.js @@ -0,0 +1,9 @@ +require("find-java-home")(function (err, home) { + if (err || !home) { + if (!err) { + err = Error("Unable to determine Java home location"); + } + process.exit(1); + } + process.stdout.write(home); +}); diff --git a/scripts/find_java_libdir.sh b/scripts/find_java_libdir.sh new file mode 100755 index 00000000..f638569e --- /dev/null +++ b/scripts/find_java_libdir.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -eu + +SCRIPT_DIR=$(dirname "$0") +cd "${SCRIPT_DIR}/.." + +error() { + echo "error: $*" >&2 + exit 1 +} + +main () { + local target_arch=$1 + local os=$2 + + local java_home full_java_version java_version + java_home=$(node ./scripts/findJavaHome.js) + full_java_version=$(${java_home}/bin/java -version 2>&1 | grep version | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') + + if [[ "${full_java_version}" = "1."* ]] + then + java_version=$(echo "${full_java_version}" | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') + else + java_version=$(echo "${full_java_version}" | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') + fi + + local jre_dir + if [[ "${java_version}" =~ ^(6|7|8)$ && "${os}" != "zos" ]]; then + jre_dir="${java_home}/jre/lib" + else + jre_dir="${java_home}/lib" + fi + + local lib_dir="" + if [[ "${os}" == "linux" && ! "${java_version}" =~ ^(6|7|8)$ ]]; then + # no arch on JDK 9+ + lib_dir="${jre_dir}/server" + elif [[ "${os}" == "linux" && "${target_arch}" == "arm" ]]; then + if [[ -d ${jre_dir}/arm/classic ]]; then lib_dir="${jre_dir}"/arm/classic; else lib_dir="${jre_dir}"/arm/server; fi + elif [[ "${os}" == "linux" && "${target_arch}" == "arm64" ]]; then + if [[ -d ${jre_dir}/aarch64/classic ]]; then lib_dir="${jre_dir}"/aarch64/classic; else lib_dir="${jre_dir}"/aarch64/server; fi + elif [[ "${os}" == "linux" && "${target_arch}" == "ia32" ]]; then + if [[ -d ${jre_dir}/i386/classic ]]; then lib_dir="${jre_dir}"/i386/classic; else lib_dir="${jre_dir}"/i386/server; fi + elif [[ "${os}" == "linux" && "${target_arch}" == "x64" ]]; then + if [[ -d ${jre_dir}/amd64/classic ]]; then lib_dir="${jre_dir}"/amd64/classic; else lib_dir="${jre_dir}"/amd64/server; fi + elif [[ "${os}" == "linux" ]] && [[ "${target_arch}" == "s390x" || "${target_arch}" == "s390" ]]; then + if [[ -d ${jre_dir}/s390x/classic ]]; then lib_dir="${jre_dir}"/s390x/classic; else lib_dir="${jre_dir}"/s390/classic; fi + elif [[ "${os}" == "zos" ]]; then + lib_dir="${jre_dir}"/s390x/classic + elif [[ "${os}" == "linux" ]] && [[ "${target_arch}" == "ppc64" || "${target_arch}" == "ppc" ]]; then + target_arch=`uname -m` + if [[ -d ${jre_dir}/${target_arch}/classic ]]; then lib_dir="${jre_dir}"/${target_arch}/classic; else lib_dir="${jre_dir}"/${target_arch}/server; fi + elif [[ "${os}" == "mac" ]]; then + if [[ -d ${jre_dir}/jli ]]; then lib_dir="${jre_dir}/jli"; else lib_dir="${jre_dir}"; fi + else + local arch + if [[ "${target_arch}" =~ (32|386) ]]; then + arch=i386 + else + arch=amd64 + fi + if [[ "${os}" == "solaris" ]]; then + lib_dir="${jre_dir}/${arch}/server" + elif [[ "${os}" == "freebsd" ]]; then + lib_dir="${jre_dir}/${arch}/server" + elif [[ "${os}" == "openbsd" ]]; then + lib_dir="${jre_dir}/${arch}/server" + fi + fi + + if [[ ! -d "${lib_dir}" ]]; then + error "Can't find lib dir '${lib_dir}' for ${os} ${target_arch}, java home: ${java_home}" + fi + echo "${lib_dir}" +} + +main "$@" diff --git a/scripts/postInstall.js b/scripts/postInstall.js new file mode 100644 index 00000000..50d3ec95 --- /dev/null +++ b/scripts/postInstall.js @@ -0,0 +1,61 @@ +const glob = require("glob"); +const fs = require("fs"); +const path = require("path"); +const os = require("os"); + +require("find-java-home")((err, home) => { + if (home) { + const dll = glob.sync("**/jvm.dll", { cwd: home })[0]; + const dylib = glob.sync("**/libjli.dylib", { cwd: home })[0]; + const soFiles = glob.sync("**/libjvm.so", { cwd: home }); + + let so; + if (soFiles.length > 0) { + so = getCorrectSoForPlatform(soFiles); + } + + const binary = dll ?? dylib ?? so; + + fs.writeFileSync( + path.resolve(__dirname, "../build/jvm_dll_path.json"), + binary ? JSON.stringify(path.delimiter + path.dirname(path.resolve(home, binary))) : '""' + ); + } +}); + +function getCorrectSoForPlatform(soFiles) { + let so = _getCorrectSoForPlatform(soFiles); + if (so) { + so = removeDuplicateJre(so); + } + return so; +} + +function removeDuplicateJre(filePath) { + while (filePath.indexOf("jre/jre") >= 0) { + filePath = filePath.replace("jre/jre", "jre"); + } + return filePath; +} + +function _getCorrectSoForPlatform(soFiles) { + const architectureFolderNames = { + ia32: "i386", + x64: "amd64", + }; + + if (os.platform() != "sunos") { + return soFiles[0]; + } + + const requiredFolderName = architectureFolderNames[os.arch()]; + + for (let i = 0; i < soFiles.length; i++) { + const so = soFiles[i]; + if (so.indexOf("server") > 0 && so.indexOf(requiredFolderName) > 0) { + return so; + } + } + + return soFiles[0]; +} diff --git a/scripts/testRunner.ts b/scripts/testRunner.ts new file mode 100644 index 00000000..fe36d289 --- /dev/null +++ b/scripts/testRunner.ts @@ -0,0 +1,55 @@ +// testRunner.js + +// This is a custom test runner. All tests are run with vitest, but in separate +// processes, which allows us to test java with different configuration options. + +import chalk from "chalk"; +import findRoot from "find-root"; +import * as glob from "glob"; +import childProcess, { ExecException } from "node:child_process"; +import path from "node:path"; + +const root = findRoot(__dirname); + +const tests = glob + .sync("*.test.ts", { cwd: path.join(root, "testAsyncOptions") }) + .sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())); + +tests.unshift("test"); // Arrange to run the primary tests first, in a single process + +async function runTest(testIndex: number, testArgs: string): Promise { + const vitest = path.join("node_modules", ".bin", "vitest"); + const cmd = testArgs === "test" ? `vitest --dir test` : `${vitest} ${testArgs}`; + console.log(chalk.cyan(`(${testIndex + 1}/${tests.length}) running "${cmd}"...`)); + return new Promise((resolve, reject) => { + childProcess.exec(cmd, (error: ExecException | null, stdout: string, stderr: string) => { + const errText = stderr.toString(); + if (errText !== "") { + console.error(chalk.bold.red(errText)); + } + + process.stdout.write(stdout.toString()); + if (error) { + return reject(error); + } + resolve(); + }); + }); +} + +console.log("test to run", tests); +async function runAll(): Promise { + for (let i = 0; i < tests.length; i++) { + const test = tests[i]; + await runTest(i, test); + } +} + +runAll() + .then(() => { + console.log(chalk.green("Tests completed successfully!")); + }) + .catch((err) => { + console.error(chalk.bold.red(err)); + process.exit(1); + }); diff --git a/scripts/update-commons-lang.sh b/scripts/update-commons-lang.sh new file mode 100755 index 00000000..e3eb71ae --- /dev/null +++ b/scripts/update-commons-lang.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +SCRIPT_DIR=$(dirname $(realpath $0)) + +cd "${SCRIPT_DIR}/commons-lang" +mvn package +cp "${SCRIPT_DIR}/commons-lang/target/commons-lang-1.jar" "${SCRIPT_DIR}/../src-java/commons-lang3-node-java.jar" + +echo "complete!" diff --git a/src-cpp/java.cpp b/src-cpp/java.cpp new file mode 100644 index 00000000..e124b4dc --- /dev/null +++ b/src-cpp/java.cpp @@ -0,0 +1,1434 @@ +#include "java.h" +#include +#ifdef WIN32 +#else +#include +#endif +#include "javaObject.h" +#include "javaScope.h" +#include "methodCallBaton.h" +#include "node_NodeDynamicProxyClass.h" +#include +#include +#include +#include + +#define DYNAMIC_PROXY_JS_ERROR -4 + +#ifdef WIN32 +typedef long threadId; +#else +typedef pthread_t threadId; +#endif + +threadId v8ThreadId; +bool isDefaultLoopRunning = false; + +std::queue queue_dynamicProxyJsCallData; +uv_mutex_t uvMutex_dynamicProxyJsCall; +uv_async_t uvAsync_dynamicProxyJsCall; + +/*static*/ Nan::Persistent Java::s_ct; +/*static*/ std::string Java::s_nativeBindingLocation; + +void my_sleep(int dur) { +#ifdef WIN32 + Sleep(dur); +#else + usleep(dur); +#endif +} + +threadId my_getThreadId() { +#ifdef WIN32 + return (long)GetCurrentThreadId(); +#else + return pthread_self(); +#endif +} + +bool v8ThreadIdEquals(threadId a, threadId b) { +#ifdef WIN32 + return a == b; +#else + return pthread_equal(a, b); +#endif +} + +void EIO_CallJs(DynamicProxyJsCallData *callData); + +void uvAsyncCb_dynamicProxyJsCall(uv_async_t *handle) { + DynamicProxyJsCallData *callData; + do { + uv_mutex_lock(&uvMutex_dynamicProxyJsCall); + if (!queue_dynamicProxyJsCallData.empty()) { + callData = queue_dynamicProxyJsCallData.front(); + queue_dynamicProxyJsCallData.pop(); + } else { + callData = NULL; + } + uv_mutex_unlock(&uvMutex_dynamicProxyJsCall); + + if (callData) { + EIO_CallJs(callData); + } + } while (callData); +} + +/*static*/ void Java::Init(v8::Local target) { + Nan::HandleScope scope; + + v8ThreadId = my_getThreadId(); + isDefaultLoopRunning = false; // init as false + + uv_mutex_init(&uvMutex_dynamicProxyJsCall); + + v8::Local t = Nan::New(New); + s_ct.Reset(t); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(Nan::New("Java").ToLocalChecked()); + + Nan::SetPrototypeMethod(t, "getClassLoader", getClassLoader); + Nan::SetPrototypeMethod(t, "newInstance", newInstance); + Nan::SetPrototypeMethod(t, "newInstanceSync", newInstanceSync); + Nan::SetPrototypeMethod(t, "newProxy", newProxy); + Nan::SetPrototypeMethod(t, "callStaticMethod", callStaticMethod); + Nan::SetPrototypeMethod(t, "callStaticMethodSync", callStaticMethodSync); + Nan::SetPrototypeMethod(t, "callMethod", callMethod); + Nan::SetPrototypeMethod(t, "callMethodSync", callMethodSync); + Nan::SetPrototypeMethod(t, "findClassSync", findClassSync); + Nan::SetPrototypeMethod(t, "newArray", newArray); + Nan::SetPrototypeMethod(t, "newByte", newByte); + Nan::SetPrototypeMethod(t, "newShort", newShort); + Nan::SetPrototypeMethod(t, "newLong", newLong); + Nan::SetPrototypeMethod(t, "newChar", newChar); + Nan::SetPrototypeMethod(t, "newFloat", newFloat); + Nan::SetPrototypeMethod(t, "newDouble", newDouble); + Nan::SetPrototypeMethod(t, "getStaticFieldValue", getStaticFieldValue); + Nan::SetPrototypeMethod(t, "setStaticFieldValue", setStaticFieldValue); + Nan::SetPrototypeMethod(t, "instanceOf", instanceOf); + Nan::SetPrototypeMethod(t, "stop", stop); + + Nan::Set(target, Nan::New("Java").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); + + JavaProxyObject::init(); +} + +NAN_METHOD(Java::New) { + Nan::HandleScope scope; + + if (!isDefaultLoopRunning) { + uv_async_init(uv_default_loop(), &uvAsync_dynamicProxyJsCall, uvAsyncCb_dynamicProxyJsCall); + isDefaultLoopRunning = true; + } + + Java *self = new Java(); + self->Wrap(info.This()); + + Nan::Set(self->handle(), Nan::New("classpath").ToLocalChecked(), Nan::New()); + Nan::Set(self->handle(), Nan::New("options").ToLocalChecked(), Nan::New()); + Nan::Set(self->handle(), Nan::New("nativeBindingLocation").ToLocalChecked(), + Nan::New("Not Set").ToLocalChecked()); + Nan::Set(self->handle(), Nan::New("asyncOptions").ToLocalChecked(), Nan::Null()); + + info.GetReturnValue().Set(info.This()); +} + +Java::Java() { + this->m_jvm = NULL; + this->m_env = NULL; + + m_SyncSuffix = "Sync"; + m_AsyncSuffix = ""; + doSync = true; + doAsync = true; + doPromise = false; +} + +Java::~Java() { this->destroyJVM(&this->m_jvm, &this->m_env); } + +v8::Local Java::ensureJvm() { + if (!m_jvm) { + v8::Local result = createJVM(&this->m_jvm, &this->m_env); + assert(result->IsNull()); + return result; + } + + return Nan::Null(); +} + +void Java::configureAsync(v8::Local &asyncOptions) { + v8::Local asyncOptionsObj = asyncOptions.As(); + + m_SyncSuffix = "invalid"; + m_AsyncSuffix = "invalid"; + m_PromiseSuffix = "invalid"; + doSync = false; + doAsync = false; + doPromise = false; + + v8::MaybeLocal maybeSuffixValue = + Nan::Get(asyncOptionsObj, Nan::New("syncSuffix").ToLocalChecked()); + v8::Local suffixValue; + if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + Nan::Utf8String utf8(suffix); + m_SyncSuffix.assign(*utf8); + doSync = true; + } + + maybeSuffixValue = Nan::Get(asyncOptionsObj, Nan::New("asyncSuffix").ToLocalChecked()); + if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + Nan::Utf8String utf8(suffix); + m_AsyncSuffix.assign(*utf8); + doAsync = true; + } + + maybeSuffixValue = Nan::Get(asyncOptionsObj, Nan::New("promiseSuffix").ToLocalChecked()); + if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + Nan::Utf8String utf8(suffix); + m_PromiseSuffix.assign(*utf8); + doPromise = true; + } + + if (doSync && doAsync) { + assert(m_SyncSuffix != m_AsyncSuffix); + } + if (doSync && doPromise) { + assert(m_SyncSuffix != m_PromiseSuffix); + } + if (doAsync && doPromise) { + assert(m_AsyncSuffix != m_PromiseSuffix); + } + + m_asyncOptions.Reset(asyncOptionsObj); +} + +v8::Local Java::createJVM(JavaVM **jvm, JNIEnv **env) { + v8::MaybeLocal maybeAsyncOptions = + Nan::Get(this->handle(), Nan::New("asyncOptions").ToLocalChecked()); + v8::Local asyncOptions; + if (maybeAsyncOptions.ToLocal(&asyncOptions) && asyncOptions->IsObject()) { + configureAsync(asyncOptions); + } + + // setup classpath + std::ostringstream classPath; + classPath << "-Djava.class.path="; + + v8::MaybeLocal maybeClassPathValue = + Nan::Get(this->handle(), Nan::New("classpath").ToLocalChecked()); + v8::Local classPathValue; + if (!maybeClassPathValue.ToLocal(&classPathValue) || !classPathValue->IsArray()) { + return Nan::TypeError("Classpath must be an array"); + } + v8::Local classPathArrayTemp = v8::Local::Cast(classPathValue); + m_classPathArray.Reset(classPathArrayTemp); + for (uint32_t i = 0; i < classPathArrayTemp->Length(); i++) { + if (i != 0) { +#ifdef WIN32 + classPath << ";"; +#else + classPath << ":"; +#endif + } + v8::Local arrayItemValue = classPathArrayTemp->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + if (!arrayItemValue->IsString()) { + return Nan::TypeError("Classpath must only contain strings"); + } + v8::Local arrayItem = arrayItemValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + Nan::Utf8String arrayItemStr(arrayItem); + classPath << *arrayItemStr; + } + + // set the native binding location + v8::Local v8NativeBindingLocation = + Nan::Get(this->handle(), Nan::New("nativeBindingLocation").ToLocalChecked()) + .FromMaybe(v8::Local()); + Nan::Utf8String nativeBindingLocationStr(v8NativeBindingLocation); + s_nativeBindingLocation = *nativeBindingLocationStr; + + // get other options + v8::Local optionsValue = + Nan::Get(this->handle(), Nan::New("options").ToLocalChecked()).FromMaybe(v8::Local()); + if (!optionsValue->IsArray()) { + return Nan::TypeError("options must be an array"); + } + v8::Local optionsArrayTemp = v8::Local::Cast(optionsValue); + m_optionsArray.Reset(optionsArrayTemp); + + // create vm options + int vmOptionsCount = optionsArrayTemp->Length() + 1; + JavaVMOption *vmOptions = new JavaVMOption[vmOptionsCount]; + // printf("classPath: %s\n", classPath.str().c_str()); + vmOptions[0].optionString = strdup(classPath.str().c_str()); + for (uint32_t i = 0; i < optionsArrayTemp->Length(); i++) { + v8::Local arrayItemValue = optionsArrayTemp->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + if (!arrayItemValue->IsString()) { + delete[] vmOptions; + return Nan::TypeError("options must only contain strings"); + } + v8::Local arrayItem = arrayItemValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + Nan::Utf8String arrayItemStr(arrayItem); + vmOptions[i + 1].optionString = strdup(*arrayItemStr); + } + + JavaVMInitArgs args; + // The JNI invocation is documented to include a function JNI_GetDefaultJavaVMInitArgs that + // was formerly called here. But the documentation from Oracle is confusing/contradictory. + // 1) It claims that the caller must set args.version before calling JNI_GetDefaultJavaVMInitArgs, which + // we did not do. + // 2) The sample code provide at the top of the doc doesn't even call JNI_GetDefaultJavaVMInitArgs. + // 3) The Oracle documentation for Java 6 through Java 8 all contain a comment "Note that in the JDK/JRE, there is no + // longer any need to call JNI_GetDefaultJavaVMInitArgs." + // 4) It seems that some platforms don't implement JNI_GetDefaultJavaVMInitArgs, or have + // marked it deprecated. + // Omitting the call to JNI_GetDefaultJavaVMInitArgs works fine on Mac and Linux with Java 7 and Java 8. + // The Oracle documentation is here: + // http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html + // http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html + // http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html + args.version = JNI_BEST_VERSION; + // JNI_GetDefaultJavaVMInitArgs(&args); // If this turns out to be necessary, it should be called here. + args.ignoreUnrecognized = false; + args.options = vmOptions; + args.nOptions = vmOptionsCount; + + JavaVM *jvmTemp; + JNI_CreateJavaVM(&jvmTemp, (void **)env, &args); + *jvm = jvmTemp; + + delete[] vmOptions; + + m_classLoader = getSystemClassLoader(*env); + + v8::Local onJvmCreated = + Nan::Get(this->handle(), Nan::New("onJvmCreated").ToLocalChecked()).FromMaybe(v8::Local()); + + // TODO: this handles sets put doesn't prevent modifing the underlying data. So java.classpath.push will still work + // which is invalid. + Nan::SetAccessor(this->handle(), Nan::New("classpath").ToLocalChecked(), + AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); + Nan::SetAccessor(this->handle(), Nan::New("options").ToLocalChecked(), AccessorProhibitsOverwritingGetter, + AccessorProhibitsOverwritingSetter); + Nan::SetAccessor(this->handle(), Nan::New("nativeBindingLocation").ToLocalChecked(), + AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); + Nan::SetAccessor(this->handle(), Nan::New("asyncOptions").ToLocalChecked(), + AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); + Nan::SetAccessor(this->handle(), Nan::New("onJvmCreated").ToLocalChecked(), + AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); + + if (onJvmCreated->IsFunction()) { + v8::Local onJvmCreatedFunc = onJvmCreated.As(); + v8::Local context = Nan::New(); + Nan::Call(onJvmCreatedFunc, context, 0, NULL); + } + + return Nan::Null(); +} + +NAN_GETTER(Java::AccessorProhibitsOverwritingGetter) { + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + Nan::HandleScope scope; + Nan::Utf8String nameStr(property); + if (!strcmp("classpath", *nameStr)) { + info.GetReturnValue().Set(Nan::New(self->m_classPathArray)); + return; + } else if (!strcmp("options", *nameStr)) { + info.GetReturnValue().Set(Nan::New(self->m_optionsArray)); + return; + } else if (!strcmp("nativeBindingLocation", *nameStr)) { + info.GetReturnValue().Set(Nan::New(Java::s_nativeBindingLocation.c_str()).ToLocalChecked()); + return; + } else if (!strcmp("asyncOptions", *nameStr)) { + info.GetReturnValue().Set(Nan::New(self->m_asyncOptions)); + return; + } else if (!strcmp("onJvmCreated", *nameStr)) { + // There is no good reason to get onJvmCreated, so just fall through to error below. + } + + std::ostringstream errStr; + errStr << "Invalid call to accessor " << *nameStr; + info.GetReturnValue().Set(Nan::Error(errStr.str().c_str())); +} + +NAN_SETTER(Java::AccessorProhibitsOverwritingSetter) { + Nan::Utf8String nameStr(property); + std::ostringstream errStr; + errStr << "Cannot set " << *nameStr << " after calling any other java function."; + Nan::ThrowError(errStr.str().c_str()); +} + +void Java::destroyJVM(JavaVM **jvm, JNIEnv **env) { + (*jvm)->DestroyJavaVM(); + *jvm = NULL; + *env = NULL; +} + +NAN_METHOD(Java::getClassLoader) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + jclass classClazz = env->FindClass("java/lang/ClassLoader"); + jmethodID class_getClassLoader = + env->GetStaticMethodID(classClazz, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); + jobject classLoader = env->CallStaticObjectMethod(classClazz, class_getClassLoader); + checkJavaException(env); + + jobject result = env->NewGlobalRef(classLoader); + info.GetReturnValue().Set(javaToV8(self, env, result)); +} + +NAN_METHOD(Java::newInstance) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_CLASSNAME(); + ARGS_BACK_CALLBACK(); + + // find class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + EXCEPTION_CALL_CALLBACK(self, "Could not find class " << className.c_str()); + info.GetReturnValue().SetUndefined(); + return; + } + + // get method + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindConstructor(env, clazz, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, className, true, info, argsStart, argsEnd); + EXCEPTION_CALL_CALLBACK(self, msg); + info.GetReturnValue().SetUndefined(); + return; + } + + // run + NewInstanceBaton *baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); + baton->run(); + + END_CALLBACK_FUNCTION("\"Constructor for class '" + << className << "' called without a callback did you mean to use the Sync version?\""); +} + +NAN_METHOD(Java::newInstanceSync) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_CLASSNAME(); + + // find class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // find method + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindConstructor(env, clazz, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, className, true, info, argsStart, argsEnd); + return Nan::ThrowError(javaExceptionToV8(self, env, msg)); + } + + // run + v8::Local callback = Nan::Null(); + NewInstanceBaton *baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); + v8::Local result = baton->runSync(); + delete baton; + if (result->IsNativeError()) { + return Nan::ThrowError(result); + } + info.GetReturnValue().Set(result); +} + +NAN_METHOD(Java::newProxy) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + + ARGS_FRONT_STRING(interfaceName); + ARGS_FRONT_OBJECT(functions); + + DynamicProxyData *dynamicProxyData = new DynamicProxyData(); + dynamicProxyData->markerStart = DYNAMIC_PROXY_DATA_MARKER_START; + dynamicProxyData->markerEnd = DYNAMIC_PROXY_DATA_MARKER_END; + dynamicProxyData->java = self; + dynamicProxyData->interfaceName = interfaceName; + dynamicProxyData->functions.Reset(functions); + + // find NodeDynamicProxyClass + std::string className = "node.NodeDynamicProxyClass"; + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class node/NodeDynamicProxyClass"; + delete dynamicProxyData; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // find constructor + jclass objectClazz = env->FindClass("java/lang/Object"); + jobjectArray methodArgs = env->NewObjectArray(2, objectClazz, NULL); + env->SetObjectArrayElement(methodArgs, 0, + v8ToJava(env, Nan::New(s_nativeBindingLocation.c_str()).ToLocalChecked())); + env->SetObjectArrayElement(methodArgs, 1, longToJavaLongObj(env, (jlong)dynamicProxyData)); + jobject method = javaFindConstructor(env, clazz, methodArgs); + if (method == NULL) { + std::ostringstream errStr; + errStr << "Could not find constructor for class node/NodeDynamicProxyClass"; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // create the NodeDynamicProxyClass + jclass constructorClazz = env->FindClass("java/lang/reflect/Constructor"); + jmethodID constructor_newInstance = + env->GetMethodID(constructorClazz, "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"); + + // printf("invoke: %s\n", javaMethodCallToString(env, m_method, constructor_newInstance, m_args).c_str()); + + // run constructor + jobject dynamicProxy = env->CallObjectMethod(method, constructor_newInstance, methodArgs); + if (env->ExceptionCheck()) { + std::ostringstream errStr; + errStr << "Error creating class"; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + jclass dynamicInterface = javaFindClass(env, interfaceName); + if (dynamicInterface == NULL) { + std::ostringstream errStr; + errStr << "Could not find interface "; + errStr << interfaceName; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + jclass classClazz = env->FindClass("java/lang/Class"); + jobjectArray classArray = env->NewObjectArray(1, classClazz, NULL); + if (classArray == NULL) { + std::ostringstream errStr; + errStr << "Could not create class array for Proxy"; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + env->SetObjectArrayElement(classArray, 0, dynamicInterface); + + jmethodID class_getClassLoader = env->GetMethodID(classClazz, "getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject classLoader = env->CallObjectMethod(dynamicInterface, class_getClassLoader); + assertNoException(env); + + if (classLoader == NULL) { + jclass objectClazz = env->FindClass("java/lang/Object"); + jmethodID object_getClass = env->GetMethodID(objectClazz, "getClass", "()Ljava/lang/Class;"); + jobject jobjClass = env->CallObjectMethod(dynamicProxy, object_getClass); + checkJavaException(env); + classLoader = env->CallObjectMethod(jobjClass, class_getClassLoader); + checkJavaException(env); + } + if (classLoader == NULL) { + std::ostringstream errStr; + errStr << "Could not get classloader for Proxy"; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // create proxy instance + jclass proxyClass = env->FindClass("java/lang/reflect/Proxy"); + jmethodID proxy_newProxyInstance = env->GetStaticMethodID( + proxyClass, "newProxyInstance", + "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;"); + jobject proxyInstance = + env->CallStaticObjectMethod(proxyClass, proxy_newProxyInstance, classLoader, classArray, dynamicProxy); + if (env->ExceptionCheck()) { + std::ostringstream errStr; + errStr << "Error creating java.lang.reflect.Proxy"; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + v8::Local result = javaToV8(self, env, proxyInstance, dynamicProxyData); + + dynamicProxyData->jsObject.Reset(result); + info.GetReturnValue().Set(result); +} + +NAN_METHOD(Java::callStaticMethod) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_CLASSNAME(); + ARGS_FRONT_STRING(methodName); + ARGS_BACK_CALLBACK(); + + // find class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + EXCEPTION_CALL_CALLBACK(self, "Could not create class " << className.c_str()); + info.GetReturnValue().SetUndefined(); + return; + } + + // find method + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindMethod(env, clazz, methodName, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); + EXCEPTION_CALL_CALLBACK(self, msg); + info.GetReturnValue().SetUndefined(); + return; + } + + // run + StaticMethodCallBaton *baton = new StaticMethodCallBaton(self, clazz, method, methodArgs, callback); + baton->run(); + + END_CALLBACK_FUNCTION("\"Static method '" << methodName + << "' called without a callback did you mean to use the Sync version?\""); +} + +NAN_METHOD(Java::callStaticMethodSync) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_CLASSNAME(); + ARGS_FRONT_STRING(methodName); + + // find class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // find method + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindMethod(env, clazz, methodName, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); + return Nan::ThrowError(javaExceptionToV8(self, env, msg)); + } + + // run + v8::Local callback = Nan::Null(); + StaticMethodCallBaton *baton = new StaticMethodCallBaton(self, clazz, method, methodArgs, callback); + v8::Local result = baton->runSync(); + delete baton; + if (result->IsNativeError()) { + Nan::ThrowError(result); + return; + } + info.GetReturnValue().Set(result); +} + +NAN_METHOD(Java::callMethodSync) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_OBJECT(instanceObj); + ARGS_FRONT_STRING(methodName); + + JavaObject *javaObj = Nan::ObjectWrap::Unwrap(instanceObj); + + // find method + jclass clazz = javaObj->getClass(); + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindMethod(env, clazz, methodName, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); + return Nan::ThrowError(javaExceptionToV8(self, env, msg)); + } + + // run + v8::Local callback = Nan::Null(); + InstanceMethodCallBaton *baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback); + v8::Local result = baton->runSync(); + delete baton; + if (result->IsNativeError()) { + return Nan::ThrowError(result); + } + info.GetReturnValue().Set(result); +} + +NAN_METHOD(Java::callMethod) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_FRONT_OBJECT(instanceObj); + ARGS_FRONT_STRING(methodName); + ARGS_BACK_CALLBACK(); + + JavaObject *javaObj = Nan::ObjectWrap::Unwrap(instanceObj); + + // find method + jclass clazz = javaObj->getClass(); + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + jobject method = javaFindMethod(env, clazz, methodName, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); + EXCEPTION_CALL_CALLBACK(self, msg); + info.GetReturnValue().SetUndefined(); + return; + } + + // run + InstanceMethodCallBaton *baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback); + baton->run(); + + END_CALLBACK_FUNCTION("\"method '" << methodName + << "' called without a callback did you mean to use the Sync version?\""); +} + +NAN_METHOD(Java::findClassSync) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + + // arguments + ARGS_FRONT_CLASSNAME(); + + // find class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // run + v8::Local result = javaToV8(self, env, clazz); + info.GetReturnValue().Set(result); +} + +NAN_METHOD(Java::newArray) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + + // arguments + ARGS_FRONT_CLASSNAME(); + + // argument - array + if (info.Length() < argsStart + 1 || !info[argsStart]->IsArray()) { + std::ostringstream errStr; + errStr << "Argument " << (argsStart + 1) << " must be an array"; + return Nan::ThrowError(Nan::TypeError(errStr.str().c_str())); + } + v8::Local arrayObj = v8::Local::Cast(info[argsStart]); + + // find class and method + jarray results; + if (strcmp(className.c_str(), "byte") == 0) { + results = env->NewByteArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass byteClazz = env->FindClass("java/lang/Byte"); + jmethodID byte_byteValue = env->GetMethodID(byteClazz, "byteValue", "()B"); + jbyte byteValues[1]; + byteValues[0] = env->CallByteMethod(val, byte_byteValue); + assertNoException(env); + env->SetByteArrayRegion((jbyteArray)results, i, 1, byteValues); + } + } + + else if (strcmp(className.c_str(), "char") == 0) { + results = env->NewCharArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass stringClazz = env->FindClass("java/lang/String"); + jmethodID string_charAt = env->GetMethodID(stringClazz, "charAt", "(I)C"); + jchar itemValues[1]; + itemValues[0] = env->CallCharMethod(val, string_charAt, 0); + checkJavaException(env); + env->SetCharArrayRegion((jcharArray)results, i, 1, itemValues); + } + } + + else if (strcmp(className.c_str(), "short") == 0) { + results = env->NewShortArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass shortClazz = env->FindClass("java/lang/Short"); + jmethodID short_shortValue = env->GetMethodID(shortClazz, "shortValue", "()S"); + jshort shortValues[1]; + shortValues[0] = env->CallShortMethod(val, short_shortValue); + assertNoException(env); + env->SetShortArrayRegion((jshortArray)results, i, 1, shortValues); + } + } + + else if (strcmp(className.c_str(), "double") == 0) { + results = env->NewDoubleArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass doubleClazz = env->FindClass("java/lang/Double"); + jmethodID double_doubleValue = env->GetMethodID(doubleClazz, "doubleValue", "()D"); + jdouble doubleValues[1]; + doubleValues[0] = env->CallDoubleMethod(val, double_doubleValue); + assertNoException(env); + env->SetDoubleArrayRegion((jdoubleArray)results, i, 1, doubleValues); + } + } + + else if (strcmp(className.c_str(), "int") == 0) { + results = env->NewIntArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass integerClazz = env->FindClass("java/lang/Integer"); + jmethodID integer_intValue = env->GetMethodID(integerClazz, "intValue", "()I"); + jint intValues[1]; + intValues[0] = env->CallIntMethod(val, integer_intValue); + assertNoException(env); + env->SetIntArrayRegion((jintArray)results, i, 1, intValues); + } + } + + else if (strcmp(className.c_str(), "float") == 0) { + results = env->NewFloatArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass floatClazz = env->FindClass("java/lang/Float"); + jmethodID float_floatValue = env->GetMethodID(floatClazz, "floatValue", "()F"); + jfloat floatValues[1]; + floatValues[0] = env->CallFloatMethod(val, float_floatValue); + checkJavaException(env); + env->SetFloatArrayRegion((jfloatArray)results, i, 1, floatValues); + } + } + + else if (strcmp(className.c_str(), "boolean") == 0) { + results = env->NewBooleanArray(arrayObj->Length()); + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + jclass booleanClazz = env->FindClass("java/lang/Boolean"); + jmethodID boolean_booleanValue = env->GetMethodID(booleanClazz, "booleanValue", "()Z"); + jboolean booleanValues[1]; + booleanValues[0] = env->CallBooleanMethod(val, boolean_booleanValue); + checkJavaException(env); + env->SetBooleanArrayRegion((jbooleanArray)results, i, 1, booleanValues); + } + } + + else { + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // create array + results = env->NewObjectArray(arrayObj->Length(), clazz, NULL); + + for (uint32_t i = 0; i < arrayObj->Length(); i++) { + v8::Local item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + jobject val = v8ToJava(env, item); + env->SetObjectArrayElement((jobjectArray)results, i, val); + if (env->ExceptionOccurred()) { + std::ostringstream errStr; + Nan::Utf8String valStr(item); + errStr << "Could not add item \"" << *valStr << "\" to array."; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + } + } + + info.GetReturnValue().Set(JavaObject::New(self, results)); +} + +NAN_METHOD(Java::newByte) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newByte only takes 1 argument")); + } + + // argument - value + if (!info[0]->IsNumber()) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); + } + + jbyte val = Nan::To(info[0]).FromJust(); + + jclass clazz = env->FindClass("java/lang/Byte"); + jmethodID constructor = env->GetMethodID(clazz, "", "(B)V"); + jobject newObj = env->NewObject(clazz, constructor, val); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); + return; +} + +NAN_METHOD(Java::newShort) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newShort only takes 1 argument")); + } + + // argument - value + if (!info[0]->IsNumber()) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); + } + + jshort val = Nan::To(info[0]).FromJust(); + + jclass clazz = env->FindClass("java/lang/Short"); + jmethodID constructor = env->GetMethodID(clazz, "", "(S)V"); + jobject newObj = env->NewObject(clazz, constructor, val); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); +} + +NAN_METHOD(Java::newLong) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newLong only takes 1 argument")); + } + + // argument - value + if (!info[0]->IsNumber()) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); + } + + jlong val = Nan::To(info[0]).FromJust(); + + jclass clazz = env->FindClass("java/lang/Long"); + jmethodID constructor = env->GetMethodID(clazz, "", "(J)V"); + jobject newObj = env->NewObject(clazz, constructor, val); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); +} + +NAN_METHOD(Java::newChar) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newChar only takes 1 argument")); + } + + // argument - value + jchar charVal; + if (info[0]->IsNumber()) { + charVal = (jchar)Nan::To(info[0]).FromJust(); + } else if (info[0]->IsString()) { + v8::Local val = info[0]->ToString(Nan::GetCurrentContext()).ToLocalChecked(); + if (val->Length() != 1) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a string of 1 character.")); + } + std::string strVal = std::string(*Nan::Utf8String(val)); + charVal = (jchar)strVal[0]; + } else { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number or string")); + } + + jclass clazz = env->FindClass("java/lang/Character"); + jmethodID constructor = env->GetMethodID(clazz, "", "(C)V"); + jobject newObj = env->NewObject(clazz, constructor, charVal); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); +} + +NAN_METHOD(Java::newFloat) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newFloat only takes 1 argument")); + } else if (!info[0]->IsNumber()) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); + } + jfloat val = (jfloat)Nan::To(info[0]).FromJust(); + + jclass clazz = env->FindClass("java/lang/Float"); + jmethodID constructor = env->GetMethodID(clazz, "", "(F)V"); + jobject newObj = env->NewObject(clazz, constructor, val); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); +} + +NAN_METHOD(Java::newDouble) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + if (info.Length() != 1) { + return Nan::ThrowError(Nan::TypeError("newDouble only takes 1 argument")); + } else if (!info[0]->IsNumber()) { + return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); + } + + jdouble val = (jdouble)Nan::To(info[0]).FromJust(); + + jclass clazz = env->FindClass("java/lang/Double"); + jmethodID constructor = env->GetMethodID(clazz, "", "(D)V"); + jobject newObj = env->NewObject(clazz, constructor, val); + + info.GetReturnValue().Set(JavaObject::New(self, newObj)); +} + +NAN_METHOD(Java::getStaticFieldValue) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + + // arguments + ARGS_FRONT_CLASSNAME(); + ARGS_FRONT_STRING(fieldName); + + // find the class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + // get the field + jobject field = javaFindField(env, clazz, fieldName); + if (field == NULL) { + std::ostringstream errStr; + errStr << "Could not find field \"" << fieldName.c_str() << "\" on class \"" << className.c_str() << "\""; + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_get = env->GetMethodID(fieldClazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + + // get field value + jobject val = env->CallObjectMethod(field, field_get, NULL); + if (env->ExceptionOccurred()) { + std::ostringstream errStr; + errStr << "Could not get field " << fieldName.c_str() << " on class " << className.c_str(); + return Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + } + + info.GetReturnValue().Set(javaToV8(self, env, val)); +} + +NAN_METHOD(Java::setStaticFieldValue) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + + // arguments + ARGS_FRONT_CLASSNAME(); + ARGS_FRONT_STRING(fieldName); + + // argument - new value + if (info.Length() < argsStart + 1) { + std::ostringstream errStr; + errStr << "setStaticFieldValue requires " << (argsStart + 1) << " arguments"; + Nan::ThrowError(Nan::TypeError(errStr.str().c_str())); + return; + } + jobject newValue = v8ToJava(env, info[argsStart]); + argsStart++; + + // find the class + jclass clazz = javaFindClass(env, className); + if (clazz == NULL) { + std::ostringstream errStr; + errStr << "Could not create class " << className.c_str(); + Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + return; + } + + // get the field + jobject field = javaFindField(env, clazz, fieldName); + if (field == NULL) { + std::ostringstream errStr; + errStr << "Could not find field \"" << fieldName.c_str() << "\" on class \"" << className.c_str() << "\""; + Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + return; + } + + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_set = env->GetMethodID(fieldClazz, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + + // printf("newValue: %s\n", javaObjectToString(env, newValue).c_str()); + + // set field value + env->CallObjectMethod(field, field_set, NULL, newValue); + if (env->ExceptionOccurred()) { + std::ostringstream errStr; + errStr << "Could not set field " << fieldName.c_str() << " on class " << className.c_str(); + Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + return; + } + + info.GetReturnValue().SetUndefined(); +} + +NAN_METHOD(Java::instanceOf) { + Nan::HandleScope scope; + Java *self = Nan::ObjectWrap::Unwrap(info.This()); + v8::Local ensureJvmResults = self->ensureJvm(); + if (!ensureJvmResults->IsNull()) { + info.GetReturnValue().Set(ensureJvmResults); + return; + } + JNIEnv *env = self->getJavaEnv(); + JavaScope javaScope(env); + + int argsStart = 0; + ARGS_FRONT_OBJECT(obj); + ARGS_FRONT_STRING(className); + + jobject instance = v8ToJava(env, obj); + if (!instance) { + // not even a Java object + info.GetReturnValue().Set(Nan::New(false)); + return; + } + + jclass clazz = javaFindClass(env, className); + if (!clazz) { + std::ostringstream errStr; + errStr << "Could not find class " << className.c_str(); + Nan::ThrowError(javaExceptionToV8(self, env, errStr.str())); + return; + } + + jboolean res = env->IsInstanceOf(instance, clazz); + info.GetReturnValue().Set(Nan::New(res)); +} + +NAN_METHOD(Java::stop) { + if (isDefaultLoopRunning) { + uv_close((uv_handle_t *)&uvAsync_dynamicProxyJsCall, NULL); + } +} + +template std::string to_string(T value) { + std::ostringstream os; + os << value; + return os.str(); +} + +void EIO_CallJs(DynamicProxyJsCallData *callData) { + DynamicProxyData *dynamicProxyData = callData->dynamicProxyData; + + assert(callData->done == 0); + + if (!dynamicProxyDataVerify(dynamicProxyData)) { + return; + } + callData->result = NULL; + + JNIEnv *env; + int ret = dynamicProxyData->java->getJvm()->GetEnv((void **)&env, JNI_BEST_VERSION); + if (ret != JNI_OK) { + callData->throwableClass = "java/lang/IllegalStateException"; + callData->throwableMessage = "Could not retrieve JNIEnv: jvm->GetEnv returned " + to_string(ret); + callData->done = DYNAMIC_PROXY_JS_ERROR; + return; + } + + Nan::HandleScope scope; + v8::Array *v8Args; + v8::Local fn; + v8::Local *argv; + int argc; + int i; + v8::Local v8Result; + jobject javaResult; + + v8::Local dynamicProxyDataFunctions = Nan::New(dynamicProxyData->functions); + v8::Local fnObj = + dynamicProxyDataFunctions + ->Get(Nan::GetCurrentContext(), Nan::New(callData->methodName.c_str()).ToLocalChecked()) + .ToLocalChecked(); + if (fnObj->IsUndefined() || fnObj->IsNull()) { + callData->throwableClass = "java/lang/NoSuchMethodError"; + callData->throwableMessage = "Could not find js function " + callData->methodName; + callData->done = DYNAMIC_PROXY_JS_ERROR; + return; + } + if (!fnObj->IsFunction()) { + callData->throwableClass = "java/lang/IllegalStateException"; + callData->throwableMessage = callData->methodName + " is not a function"; + callData->done = DYNAMIC_PROXY_JS_ERROR; + return; + } + + fn = fnObj.As(); + + if (callData->args) { + v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, callData->args)); + argc = v8Args->Length(); + } else { + argc = 0; + } + argv = new v8::Local[argc]; + for (i = 0; i < argc; i++) { + argv[i] = v8Args->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + } + + Nan::TryCatch tryCatch; + tryCatch.SetCaptureMessage(true); + v8Result = Nan::Call(fn, dynamicProxyDataFunctions, argc, argv).FromMaybe(v8::Local()); + delete[] argv; + if (tryCatch.HasCaught()) { + callData->throwableClass = "node/NodeJsException"; + Nan::Utf8String message(tryCatch.Message()->Get()); + callData->throwableMessage = std::string(*message); + tryCatch.Reset(); + callData->done = DYNAMIC_PROXY_JS_ERROR; + return; + } + + if (!dynamicProxyDataVerify(dynamicProxyData)) { + return; + } + + javaResult = v8ToJava(env, v8Result); + if (javaResult == NULL) { + callData->result = NULL; + } else { + callData->result = env->NewGlobalRef(javaResult); + } + + callData->done = true; +} + +void throwNewThrowable(JNIEnv *env, const char *excClassName, std::string msg) { + jclass newExcCls = env->FindClass(excClassName); + jthrowable throwable = env->ExceptionOccurred(); + if (throwable != NULL) { + env->Throw(throwable); // this should only be Errors, according to the docs + } + env->ThrowNew(newExcCls, msg.c_str()); +} + +JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, + jobjectArray args) { + threadId myThreadId = my_getThreadId(); + + bool hasArgsGlobalRef = false; + + DynamicProxyData *dynamicProxyData = (DynamicProxyData *)ptr; + + // args needs to be global, you can't send env across thread boundaries + DynamicProxyJsCallData callData; + callData.dynamicProxyData = dynamicProxyData; + callData.args = args; + callData.done = false; + callData.result = NULL; + callData.throwableClass = ""; + callData.throwableMessage = ""; + + jclass methodClazz = env->FindClass("java/lang/reflect/Method"); + jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;"); + callData.methodName = javaObjectToString(env, env->CallObjectMethod(method, method_getName)); + assertNoException(env); + + if (v8ThreadIdEquals(myThreadId, v8ThreadId)) { + EIO_CallJs(&callData); + } else { + if (args) { + // if args is not null and we have to kick this across the thread boundary, make it a global ref + callData.args = (jobjectArray)env->NewGlobalRef(args); + hasArgsGlobalRef = true; + } + + uv_mutex_lock(&uvMutex_dynamicProxyJsCall); + queue_dynamicProxyJsCallData.push(&callData); // we wait for work to finish, so ok to pass ref to local var + uv_mutex_unlock(&uvMutex_dynamicProxyJsCall); + uv_async_send(&uvAsync_dynamicProxyJsCall); + + while (!callData.done) { + my_sleep(100); + } + } + + if (!dynamicProxyDataVerify(dynamicProxyData)) { + throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted"); + } + if (hasArgsGlobalRef) { + env->DeleteGlobalRef(callData.args); + } + + if (callData.done == DYNAMIC_PROXY_JS_ERROR) { + throwNewThrowable(env, callData.throwableClass.c_str(), callData.throwableMessage); + } + + jobject result = NULL; + if (callData.result) { + // need to retain a local ref so that we can return it, otherwise the returned object gets corrupted + result = env->NewLocalRef(callData.result); + env->DeleteGlobalRef(callData.result); + } + return result; +} + +JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *env, jobject src, jlong ptr) { + DynamicProxyData *dynamicProxyData = (DynamicProxyData *)ptr; + unref(dynamicProxyData); +} diff --git a/src-cpp/java.h b/src-cpp/java.h new file mode 100644 index 00000000..83c5cfcd --- /dev/null +++ b/src-cpp/java.h @@ -0,0 +1,85 @@ + +#ifndef _node_java_h_ +#define _node_java_h_ + +#include +#include +#include +#include +#include + +#ifdef JNI_VERSION_1_8 +#define JNI_BEST_VERSION JNI_VERSION_1_8 +#else +#define JNI_BEST_VERSION JNI_VERSION_1_6 +#endif + +class Java : public Nan::ObjectWrap { +public: + static void Init(v8::Local target); + JavaVM *getJvm() { return m_jvm; } + JNIEnv *getJavaEnv() { + return m_env; + } // can only be used safely by the main thread as this is the thread it belongs to + jobject getClassLoader() { return m_classLoader; } + +public: + bool DoSync() const { return doSync; } + bool DoAsync() const { return doAsync; } + bool DoPromise() const { return doPromise; } + std::string SyncSuffix() const { return m_SyncSuffix; } + std::string AsyncSuffix() const { return m_AsyncSuffix; } + std::string PromiseSuffix() const { return m_PromiseSuffix; } + +private: + Java(); + ~Java(); + v8::Local createJVM(JavaVM **jvm, JNIEnv **env); + void destroyJVM(JavaVM **jvm, JNIEnv **env); + void configureAsync(v8::Local &asyncOptions); + + static NAN_METHOD(New); + static NAN_METHOD(getClassLoader); + static NAN_METHOD(newInstance); + static NAN_METHOD(newInstanceSync); + static NAN_METHOD(newProxy); + static NAN_METHOD(callStaticMethod); + static NAN_METHOD(callStaticMethodSync); + static NAN_METHOD(callMethod); + static NAN_METHOD(callMethodSync); + static NAN_METHOD(findClassSync); + static NAN_METHOD(newArray); + static NAN_METHOD(newByte); + static NAN_METHOD(newChar); + static NAN_METHOD(newShort); + static NAN_METHOD(newLong); + static NAN_METHOD(newFloat); + static NAN_METHOD(newDouble); + static NAN_METHOD(getStaticFieldValue); + static NAN_METHOD(setStaticFieldValue); + static NAN_METHOD(instanceOf); + static NAN_METHOD(stop); + static NAN_GETTER(AccessorProhibitsOverwritingGetter); + static NAN_SETTER(AccessorProhibitsOverwritingSetter); + v8::Local ensureJvm(); + + static Nan::Persistent s_ct; + JavaVM *m_jvm; + JNIEnv *m_env; // can only be used safely by the main thread as this is the thread it belongs to + jobject m_classLoader; + std::string m_classPath; + static std::string s_nativeBindingLocation; + Nan::Persistent m_classPathArray; + Nan::Persistent m_optionsArray; + Nan::Persistent m_asyncOptions; + + std::string m_SyncSuffix; + std::string m_AsyncSuffix; + std::string m_PromiseSuffix; + + bool doSync; + bool doAsync; + bool doPromise; +}; + +#endif diff --git a/src-cpp/javaObject.cpp b/src-cpp/javaObject.cpp new file mode 100644 index 00000000..0dd23920 --- /dev/null +++ b/src-cpp/javaObject.cpp @@ -0,0 +1,393 @@ +#include "javaObject.h" +#include "java.h" +#include "javaScope.h" +#include "utils.h" +#include +#include + +/*static*/ std::map *> JavaObject::sFunctionTemplates; + +/*static*/ void JavaObject::Init(v8::Local target) {} + +/*static*/ v8::Local JavaObject::New(Java *java, jobject obj) { + Nan::EscapableHandleScope scope; + + JNIEnv *env = java->getJavaEnv(); + JavaScope javaScope(env); + + jclass objClazz = env->GetObjectClass(obj); + jclass classClazz = env->FindClass("java/lang/Class"); + jmethodID class_getName = env->GetMethodID(classClazz, "getName", "()Ljava/lang/String;"); + jobject classNameJava = env->CallObjectMethod(objClazz, class_getName); + checkJavaException(env); + std::string className = javaObjectToString(env, classNameJava); + std::replace(className.begin(), className.end(), '.', '_'); + std::replace(className.begin(), className.end(), '$', '_'); + std::replace(className.begin(), className.end(), '[', 'a'); + className = "nodeJava_" + className; + + v8::Local promisify; + if (java->DoPromise()) { + v8::Local promisifyValue = + java->handle() + ->Get(Nan::GetCurrentContext(), Nan::New("promisify").ToLocalChecked()) + .ToLocalChecked(); + promisify = promisifyValue.As(); + } + + v8::Local funcTemplate; + if (sFunctionTemplates.find(className) != sFunctionTemplates.end()) { + // printf("existing className: %s\n", className.c_str()); + funcTemplate = Nan::New(*sFunctionTemplates[className]); + } else { + // printf("create className: %s\n", className.c_str()); + + funcTemplate = Nan::New(); + funcTemplate->InstanceTemplate()->SetInternalFieldCount(1); + funcTemplate->SetClassName(Nan::New(className.c_str()).ToLocalChecked()); + + // copy methods to template + std::list methods; + javaReflectionGetMethods(env, objClazz, &methods, false); + jclass methodClazz = env->FindClass("java/lang/reflect/Method"); + jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;"); + for (std::list::iterator it = methods.begin(); it != methods.end(); ++it) { + jstring methodNameJava = (jstring)env->CallObjectMethod(*it, method_getName); + assertNoException(env); + std::string methodNameStr = javaToString(env, methodNameJava); + + v8::Local baseMethodName = Nan::New(methodNameStr.c_str()).ToLocalChecked(); + + std::string methodNameAsyncStr = methodNameStr; + const char *methodNameAsync = methodNameAsyncStr.append(java->AsyncSuffix()).c_str(); + v8::Local methodCallTemplate = Nan::New(methodCall, baseMethodName); + Nan::SetPrototypeTemplate(funcTemplate, methodNameAsync, methodCallTemplate); + + std::string methodNameSyncStr = methodNameStr; + const char *methodNameSync = methodNameSyncStr.append(java->SyncSuffix()).c_str(); + v8::Local methodCallSyncTemplate = + Nan::New(methodCallSync, baseMethodName); + Nan::SetPrototypeTemplate(funcTemplate, methodNameSync, methodCallSyncTemplate); + + if (java->DoPromise()) { + v8::Local recv = Nan::New(); + v8::Local argv[] = {methodCallTemplate->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()}; + v8::Local result = Nan::Call(promisify, recv, 1, argv).FromMaybe(v8::Local()); + if (!result->IsFunction()) { + fprintf(stderr, "Promisified result is not a function.\n"); + assert(result->IsFunction()); + } + v8::Local promFunction = result.As(); + v8::Local promFunctionTemplate = + Nan::New(methodCallPromise, promFunction); + std::string methodNamePromiseStr = methodNameStr; + const char *methodNamePromise = methodNamePromiseStr.append(java->PromiseSuffix()).c_str(); + Nan::SetPrototypeTemplate(funcTemplate, methodNamePromise, promFunctionTemplate); + } + } + + // copy fields to template + std::list fields; + javaReflectionGetFields(env, objClazz, &fields); + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_getName = env->GetMethodID(fieldClazz, "getName", "()Ljava/lang/String;"); + for (std::list::iterator it = fields.begin(); it != fields.end(); ++it) { + jstring fieldNameJava = (jstring)env->CallObjectMethod(*it, field_getName); + checkJavaException(env); + std::string fieldNameStr = javaToString(env, fieldNameJava); + + v8::Local fieldName = Nan::New(fieldNameStr.c_str()).ToLocalChecked(); + Nan::SetAccessor(funcTemplate->InstanceTemplate(), fieldName, fieldGetter, fieldSetter); + } + + // copy array methods to template + jmethodID class_isArray = env->GetMethodID(classClazz, "isArray", "()Z"); + jboolean isArray = env->CallBooleanMethod(objClazz, class_isArray); + if (isArray) { + v8::Local fieldName = Nan::New("length").ToLocalChecked(); + Nan::SetAccessor(funcTemplate->InstanceTemplate(), fieldName, fieldGetter, NULL); + + Nan::SetIndexedPropertyHandler(funcTemplate->InstanceTemplate(), indexGetter); + } + + Nan::Persistent *persistentFuncTemplate = new Nan::Persistent(); + persistentFuncTemplate->Reset(funcTemplate); + sFunctionTemplates[className] = persistentFuncTemplate; + } + + v8::Local ctor = Nan::GetFunction(funcTemplate).ToLocalChecked(); + v8::Local javaObjectObj = Nan::NewInstance(ctor).ToLocalChecked(); + SetHiddenValue(javaObjectObj, Nan::New(V8_HIDDEN_MARKER_JAVA_OBJECT).ToLocalChecked(), + Nan::New(true)); + JavaObject *self = new JavaObject(java, obj); + self->Wrap(javaObjectObj); + + return scope.Escape(javaObjectObj); +} + +JavaObject::JavaObject(Java *java, jobject obj) { + m_java = java; + JNIEnv *env = m_java->getJavaEnv(); + m_obj = env->NewGlobalRef(obj); + m_class = (jclass)env->NewGlobalRef(env->GetObjectClass(obj)); +} + +JavaObject::~JavaObject() { + JNIEnv *env = m_java->getJavaEnv(); + env->DeleteGlobalRef(m_obj); + env->DeleteGlobalRef(m_class); +} + +NAN_METHOD(JavaObject::methodCall) { + Nan::HandleScope scope; + JavaObject *self = Nan::ObjectWrap::Unwrap(info.This()); + JNIEnv *env = self->m_java->getJavaEnv(); + JavaScope javaScope(env); + + Nan::Utf8String methodName(info.Data()); + std::string methodNameStr = *methodName; + + int argsStart = 0; + int argsEnd = info.Length(); + + // arguments + ARGS_BACK_CALLBACK(); + + if (!callbackProvided && methodNameStr == "toString") { + return methodCallSync(info); + } + + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + + jobject method = javaFindMethod(env, self->m_class, methodNameStr, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, self->m_class, methodNameStr, false, info, argsStart, argsEnd); + EXCEPTION_CALL_CALLBACK(self->m_java, msg); + info.GetReturnValue().SetUndefined(); + return; + } + + // run + InstanceMethodCallBaton *baton = new InstanceMethodCallBaton(self->m_java, self, method, methodArgs, callback); + baton->run(); + + END_CALLBACK_FUNCTION("\"Method '" << methodNameStr + << "' called without a callback did you mean to use the Sync version?\""); +} + +NAN_METHOD(JavaObject::methodCallSync) { + Nan::HandleScope scope; + JavaObject *self = Nan::ObjectWrap::Unwrap(info.This()); + JNIEnv *env = self->m_java->getJavaEnv(); + JavaScope javaScope(env); + + Nan::Utf8String methodName(info.Data()); + std::string methodNameStr = *methodName; + + int argsStart = 0; + int argsEnd = info.Length(); + + jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); + + jobject method = javaFindMethod(env, self->m_class, methodNameStr, methodArgs); + if (method == NULL) { + std::string msg = methodNotFoundToString(env, self->m_class, methodNameStr, false, info, argsStart, argsEnd); + v8::Local ex = javaExceptionToV8(self->m_java, env, msg); + Nan::ThrowError(ex); + return; + } + + // run + v8::Local callback = Nan::Undefined(); + InstanceMethodCallBaton *baton = new InstanceMethodCallBaton(self->m_java, self, method, methodArgs, callback); + v8::Local result = baton->runSync(); + delete baton; + + if (result->IsNativeError()) { + Nan::ThrowError(result); + return; + } + + info.GetReturnValue().Set(result); +} + +NAN_METHOD(JavaObject::methodCallPromise) { + Nan::HandleScope scope; + v8::Local fn = info.Data().As(); + v8::Local *argv = new v8::Local[info.Length()]; + for (int i = 0; i < info.Length(); i++) { + argv[i] = info[i]; + } + + v8::MaybeLocal result = Nan::Call(fn, info.This(), info.Length(), argv); + + delete[] argv; + + if (!result.IsEmpty()) { + info.GetReturnValue().Set(result.ToLocalChecked()); + } +} + +NAN_GETTER(JavaObject::fieldGetter) { + Nan::HandleScope scope; + JavaObject *self = Nan::ObjectWrap::Unwrap(info.This()); + JNIEnv *env = self->m_java->getJavaEnv(); + JavaScope javaScope(env); + + Nan::Utf8String propertyCStr(property); + std::string propertyStr = *propertyCStr; + jobject field = javaFindField(env, self->m_class, propertyStr); + if (field == NULL) { + if (propertyStr == "length") { + jclass classClazz = env->FindClass("java/lang/Class"); + jmethodID class_isArray = env->GetMethodID(classClazz, "isArray", "()Z"); + jboolean isArray = env->CallBooleanMethod(self->m_class, class_isArray); + if (isArray) { + jclass arrayClass = env->FindClass("java/lang/reflect/Array"); + jmethodID array_getLength = env->GetStaticMethodID(arrayClass, "getLength", "(Ljava/lang/Object;)I"); + jint arrayLength = env->CallStaticIntMethod(arrayClass, array_getLength, self->m_obj); + assertNoException(env); + info.GetReturnValue().Set(Nan::New(static_cast(arrayLength))); + return; + } + } + + std::ostringstream errStr; + errStr << "Could not find field \"" << propertyStr << "\" for get"; + v8::Local ex = javaExceptionToV8(self->m_java, env, errStr.str()); + Nan::ThrowError(ex); + return; + } + + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_get = env->GetMethodID(fieldClazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + + // get field value + jobject val = env->CallObjectMethod(field, field_get, self->m_obj); + if (env->ExceptionOccurred()) { + std::ostringstream errStr; + errStr << "Could not get field " << propertyStr; + v8::Local ex = javaExceptionToV8(self->m_java, env, errStr.str()); + Nan::ThrowError(ex); + return; + } + + v8::Local result = javaToV8(self->m_java, env, val); + + info.GetReturnValue().Set(result); +} + +NAN_SETTER(JavaObject::fieldSetter) { + Nan::HandleScope scope; + JavaObject *self = Nan::ObjectWrap::Unwrap(info.This()); + JNIEnv *env = self->m_java->getJavaEnv(); + JavaScope javaScope(env); + + jobject newValue = v8ToJava(env, value); + + Nan::Utf8String propertyCStr(property); + std::string propertyStr = *propertyCStr; + jobject field = javaFindField(env, self->m_class, propertyStr); + if (field == NULL) { + std::ostringstream errStr; + errStr << "Could not find field \"" << propertyStr << "\" for set"; + v8::Local error = javaExceptionToV8(self->m_java, env, errStr.str()); + Nan::ThrowError(error); + return; + } + + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_set = env->GetMethodID(fieldClazz, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + + // printf("newValue: %s\n", javaObjectToString(env, newValue).c_str()); + + // set field value + env->CallObjectMethod(field, field_set, self->m_obj, newValue); + if (env->ExceptionOccurred()) { + std::ostringstream errStr; + errStr << "Could not set field " << propertyStr; + v8::Local error = javaExceptionToV8(self->m_java, env, errStr.str()); + Nan::ThrowError(error); + return; + } +} + +NAN_INDEX_GETTER(JavaObject::indexGetter) { + Nan::HandleScope scope; + JavaObject *self = Nan::ObjectWrap::Unwrap(info.This()); + JNIEnv *env = self->m_java->getJavaEnv(); + JavaScope javaScope(env); + + jclass arrayClass = env->FindClass("java/lang/reflect/Array"); + + jmethodID array_getLength = env->GetStaticMethodID(arrayClass, "getLength", "(Ljava/lang/Object;)I"); + jint arrayLength = env->CallStaticIntMethod(arrayClass, array_getLength, self->m_obj); + assertNoException(env); + if ((jint)index >= arrayLength) { + info.GetReturnValue().SetUndefined(); + RETURN_INTERCEPTED_YES; + } + + jmethodID array_get = env->GetStaticMethodID(arrayClass, "get", "(Ljava/lang/Object;I)Ljava/lang/Object;"); + jobject item = env->CallStaticObjectMethod(arrayClass, array_get, self->m_obj, index); + assertNoException(env); + v8::Local result = javaToV8(self->m_java, env, item); + info.GetReturnValue().Set(result); + RETURN_INTERCEPTED_YES; +} + +/*static*/ Nan::Persistent JavaProxyObject::s_proxyCt; + +/*static*/ void JavaProxyObject::init() { + v8::Local t = Nan::New(); + s_proxyCt.Reset(t); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(Nan::New("NodeDynamicProxy").ToLocalChecked()); + + Nan::SetPrototypeTemplate(t, "unref", Nan::New(doUnref)); + + v8::Local fieldName = Nan::New("invocationHandler").ToLocalChecked(); + Nan::SetAccessor(t->InstanceTemplate(), fieldName, invocationHandlerGetter); +} + +v8::Local JavaProxyObject::New(Java *java, jobject obj, DynamicProxyData *dynamicProxyData) { + Nan::EscapableHandleScope scope; + + v8::Local ctor = Nan::New(s_proxyCt)->GetFunction(Nan::GetCurrentContext()).ToLocalChecked(); + v8::Local javaObjectObj = Nan::NewInstance(ctor).ToLocalChecked(); + SetHiddenValue(javaObjectObj, Nan::New(V8_HIDDEN_MARKER_JAVA_OBJECT).ToLocalChecked(), + Nan::New(true)); + JavaProxyObject *self = new JavaProxyObject(java, obj, dynamicProxyData); + self->Wrap(javaObjectObj); + + return scope.Escape(javaObjectObj); +} + +JavaProxyObject::JavaProxyObject(Java *java, jobject obj, DynamicProxyData *dynamicProxyData) : JavaObject(java, obj) { + m_dynamicProxyData = dynamicProxyData; +} + +JavaProxyObject::~JavaProxyObject() { + if (dynamicProxyDataVerify(m_dynamicProxyData)) { + unref(m_dynamicProxyData); + } +} + +NAN_METHOD(JavaProxyObject::doUnref) { + JavaProxyObject *self = Nan::ObjectWrap::Unwrap(info.This()); + if (dynamicProxyDataVerify(self->m_dynamicProxyData)) { + unref(self->m_dynamicProxyData); + } + info.GetReturnValue().SetUndefined(); +} + +NAN_GETTER(JavaProxyObject::invocationHandlerGetter) { + Nan::HandleScope scope; + + JavaProxyObject *self = Nan::ObjectWrap::Unwrap(info.This()); + if (!dynamicProxyDataVerify(self->m_dynamicProxyData)) { + Nan::ThrowError("dynamicProxyData has been destroyed or corrupted"); + return; + } + info.GetReturnValue().Set(Nan::New(self->m_dynamicProxyData->functions)); +} diff --git a/src-cpp/javaObject.h b/src-cpp/javaObject.h new file mode 100644 index 00000000..b3dc6529 --- /dev/null +++ b/src-cpp/javaObject.h @@ -0,0 +1,59 @@ + +#ifndef _javaobject_h_ +#define _javaobject_h_ + +#include "methodCallBaton.h" +#include +#include +#include +#include +#include + +class Java; + +class JavaObject : public Nan::ObjectWrap { +public: + static void Init(v8::Local target); + static v8::Local New(Java *java, jobject obj); + static v8::Local NewProxy(Java *java, jobject obj, DynamicProxyData *dynamicProxyData); + + jobject getObject() { return m_obj; } + jclass getClass() { return m_class; } + + void Ref() { Nan::ObjectWrap::Ref(); } + void Unref() { Nan::ObjectWrap::Unref(); } + +protected: + JavaObject(Java *java, jobject obj); + ~JavaObject(); + +private: + static NAN_METHOD(methodCall); + static NAN_METHOD(methodCallSync); + static NAN_METHOD(methodCallPromise); + static NAN_GETTER(fieldGetter); + static NAN_SETTER(fieldSetter); + static NAN_INDEX_GETTER(indexGetter); + + static std::map *> sFunctionTemplates; + Java *m_java; + jobject m_obj; + jclass m_class; +}; + +class JavaProxyObject : public JavaObject { +public: + static void init(); + static v8::Local New(Java *java, jobject obj, DynamicProxyData *dynamicProxyData); + +private: + JavaProxyObject(Java *java, jobject obj, DynamicProxyData *dynamicProxyData); + ~JavaProxyObject(); + static NAN_METHOD(doUnref); + static NAN_GETTER(invocationHandlerGetter); + + static Nan::Persistent s_proxyCt; + DynamicProxyData *m_dynamicProxyData; +}; + +#endif diff --git a/src-cpp/javaScope.cpp b/src-cpp/javaScope.cpp new file mode 100644 index 00000000..5c8b46bb --- /dev/null +++ b/src-cpp/javaScope.cpp @@ -0,0 +1,15 @@ + +#include "javaScope.h" + +JavaScope::JavaScope(JNIEnv *env) { + m_env = env; + m_result = NULL; + m_env->PushLocalFrame(LOCAL_FRAME_SIZE); +} + +JavaScope::~JavaScope() { m_env->PopLocalFrame(m_result); } + +jobject JavaScope::Close(jobject result) { + m_result = result; + return m_result; +} \ No newline at end of file diff --git a/src-cpp/javaScope.h b/src-cpp/javaScope.h new file mode 100644 index 00000000..033457a5 --- /dev/null +++ b/src-cpp/javaScope.h @@ -0,0 +1,20 @@ + +#ifndef _javaScope_h_ +#define _javaScope_h_ + +#include + +#define LOCAL_FRAME_SIZE 500 + +class JavaScope { +public: + JavaScope(JNIEnv *env); + ~JavaScope(); + jobject Close(jobject result); + +private: + JNIEnv *m_env; + jobject m_result; +}; + +#endif diff --git a/src-cpp/methodCallBaton.cpp b/src-cpp/methodCallBaton.cpp new file mode 100644 index 00000000..7f2d1836 --- /dev/null +++ b/src-cpp/methodCallBaton.cpp @@ -0,0 +1,234 @@ + +#include "methodCallBaton.h" +#include "java.h" +#include "javaObject.h" +#include "javaScope.h" + +jmethodID MethodCallBaton::m_methodInvokeMethodId = 0; + +Nan::Callback *toNanCallback(v8::Local &callback) { + if (callback->IsFunction()) { + return new Nan::Callback(callback.As()); + } + return NULL; +} + +MethodCallBaton::MethodCallBaton(Java *java, jobject method, jarray args, v8::Local &callback) + : Nan::AsyncWorker(toNanCallback(callback)) { + JNIEnv *env = java->getJavaEnv(); + m_java = java; + m_args = (jarray)env->NewGlobalRef(args); + m_method = env->NewGlobalRef(method); + m_error = NULL; + m_result = NULL; +} + +MethodCallBaton::~MethodCallBaton() { + JNIEnv *env = m_java->getJavaEnv(); + + if (m_result) { + env->DeleteGlobalRef(m_result); + m_result = NULL; + } + + if (m_error) { + env->DeleteGlobalRef(m_error); + m_error = NULL; + } + + env->DeleteGlobalRef(m_args); + m_args = NULL; + + env->DeleteGlobalRef(m_method); + m_method = NULL; +} + +jmethodID MethodCallBaton::getMethodInvokeMethodId(JNIEnv *env) { + if (m_methodInvokeMethodId == 0) { + jclass methodClazz = env->FindClass("java/lang/reflect/Method"); + m_methodInvokeMethodId = + env->GetMethodID(methodClazz, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + } + return m_methodInvokeMethodId; +} + +void MethodCallBaton::run() { Nan::AsyncQueueWorker(this); } + +v8::Local MethodCallBaton::runSync() { + JNIEnv *env = m_java->getJavaEnv(); + ExecuteInternal(env); + return resultsToV8(env); +} + +// called by NanAsyncWorker. This will be on a worker thread +void MethodCallBaton::Execute() { + JNIEnv *env = javaGetEnv(this->m_java->getJvm(), this->m_java->getClassLoader()); + JavaScope javaScope(env); + ExecuteInternal(env); +} + +// callback from NanAsyncWorker. This will be on the v8 main thread +void MethodCallBaton::WorkComplete() { + Nan::HandleScope scope; + + if (callback) { + JNIEnv *env = javaGetEnv(this->m_java->getJvm(), this->m_java->getClassLoader()); + JavaScope javaScope(env); + v8::Local result = resultsToV8(env); + if (result->IsNativeError()) { + v8::Local argv[] = {result}; + callback->Call(1, argv, async_resource); + } else { + v8::Local argv[] = {Nan::Undefined(), result}; + callback->Call(2, argv, async_resource); + } + + delete callback; + callback = NULL; + } +} + +v8::Local MethodCallBaton::resultsToV8(JNIEnv *env) { + Nan::EscapableHandleScope scope; + + if (m_error) { + jthrowable cause = m_error; + + // if we've caught an InvocationTargetException exception, + // let's grab the cause. users don't necessarily know that + // we're invoking the methods through reflection + jclass invocationExceptionClazz = env->FindClass("java/lang/reflect/InvocationTargetException"); + if (env->IsInstanceOf(m_error, invocationExceptionClazz)) { + jclass throwableClazz = env->FindClass("java/lang/Throwable"); + jmethodID throwable_getCause = env->GetMethodID(throwableClazz, "getCause", "()Ljava/lang/Throwable;"); + cause = (jthrowable)env->CallObjectMethod(m_error, throwable_getCause); + checkJavaException(env); + } + + v8::Local err = javaExceptionToV8(m_java, env, cause, m_errorString); + return scope.Escape(err); + } + + return scope.Escape(javaToV8(m_java, env, m_result)); +} + +void NewInstanceBaton::ExecuteInternal(JNIEnv *env) { + jclass batonClazz = env->FindClass("node/MethodCallBaton"); + jmethodID newInstance = env->GetStaticMethodID( + batonClazz, "newInstance", "(Ljava/lang/reflect/Constructor;[Ljava/lang/Object;)Ljava/lang/Object;"); + + jarray args = javaGetArgsForConstructor(env, m_method, m_args); + jobject result = env->CallStaticObjectMethod(batonClazz, newInstance, m_method, args); + + if (env->ExceptionCheck()) { + jthrowable ex = env->ExceptionOccurred(); + env->ExceptionClear(); + m_error = (jthrowable)env->NewGlobalRef(ex); + m_errorString = "Error creating class"; + return; + } + + m_result = env->NewGlobalRef(result); +} + +void StaticMethodCallBaton::ExecuteInternal(JNIEnv *env) { + /* + printf("calling %s\n", javaObjectToString(env, m_method).c_str()); + printf("arguments\n"); + for(int i=0; iGetArrayLength(m_args); i++) { + jobject o = env->GetObjectArrayElement((jobjectArray)m_args, i); + jclass c = env->GetObjectClass(o); + printf(" %s (%s)\n", javaObjectToString(env, o).c_str(), javaObjectToString(env, c).c_str()); + } + */ + + jclass batonClazz = env->FindClass("node/MethodCallBaton"); + jmethodID invokeMethod = + env->GetStaticMethodID(batonClazz, env->GetVersion() >= 0x90000 ? "invokeMethod9" : "invokeMethod", + "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + + jarray args = javaGetArgsForMethod(env, m_method, m_args); + jobject result = env->CallStaticObjectMethod(batonClazz, invokeMethod, m_method, NULL, args); + + if (env->ExceptionCheck()) { + jthrowable ex = env->ExceptionOccurred(); + env->ExceptionClear(); + m_error = (jthrowable)env->NewGlobalRef(ex); + m_errorString = "Error running static method"; + return; + } + + m_result = env->NewGlobalRef(result); +} + +void InstanceMethodCallBaton::ExecuteInternal(JNIEnv *env) { + /* + printf("calling %s\n", javaObjectToString(env, m_method).c_str()); + printf("arguments\n"); + for(int i=0; iGetArrayLength(m_args); i++) { + printf(" %s\n", javaObjectToString(env, env->GetObjectArrayElement((jobjectArray)m_args, i)).c_str()); + } + */ + + jclass batonClazz = env->FindClass("node/MethodCallBaton"); + jmethodID invokeMethod = + env->GetStaticMethodID(batonClazz, env->GetVersion() >= 0x90000 ? "invokeMethod9" : "invokeMethod", + "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + + jarray args = javaGetArgsForMethod(env, m_method, m_args); + jobject result = env->CallStaticObjectMethod(batonClazz, invokeMethod, m_method, m_javaObject->getObject(), args); + + if (env->ExceptionCheck()) { + jthrowable ex = env->ExceptionOccurred(); + env->ExceptionClear(); + m_error = (jthrowable)env->NewGlobalRef(ex); + m_errorString = "Error running instance method"; + return; + } + + if (result == NULL) { + m_result = NULL; + } else { + m_result = env->NewGlobalRef(result); + } +} + +NewInstanceBaton::NewInstanceBaton(Java *java, jclass clazz, jobject method, jarray args, + v8::Local &callback) + : MethodCallBaton(java, method, args, callback) { + JNIEnv *env = m_java->getJavaEnv(); + m_clazz = (jclass)env->NewGlobalRef(clazz); +} + +NewInstanceBaton::~NewInstanceBaton() { + JNIEnv *env = m_java->getJavaEnv(); + env->DeleteGlobalRef(m_clazz); + m_clazz = NULL; +} + +StaticMethodCallBaton::StaticMethodCallBaton(Java *java, jclass clazz, jobject method, jarray args, + v8::Local &callback) + : MethodCallBaton(java, method, args, callback) { + JNIEnv *env = m_java->getJavaEnv(); + m_clazz = (jclass)env->NewGlobalRef(clazz); +} + +StaticMethodCallBaton::~StaticMethodCallBaton() { + JNIEnv *env = m_java->getJavaEnv(); + env->DeleteGlobalRef(m_clazz); + m_clazz = NULL; +} + +InstanceMethodCallBaton::InstanceMethodCallBaton(Java *java, JavaObject *obj, jobject method, jarray args, + v8::Local &callback) + : MethodCallBaton(java, method, args, callback) { + m_javaObject = obj; + m_javaObject->Ref(); +} + +InstanceMethodCallBaton::~InstanceMethodCallBaton() { + if (m_javaObject) { + m_javaObject->Unref(); + m_javaObject = NULL; + } +} diff --git a/src-cpp/methodCallBaton.h b/src-cpp/methodCallBaton.h new file mode 100644 index 00000000..ee4102fa --- /dev/null +++ b/src-cpp/methodCallBaton.h @@ -0,0 +1,74 @@ + +#ifndef _methodcallbaton_h_ +#define _methodcallbaton_h_ + +#include "utils.h" +#include +#include +#include +#include +#include + +class Java; +class JavaObject; + +class MethodCallBaton : public Nan::AsyncWorker { +public: + MethodCallBaton(Java *java, jobject method, jarray args, v8::Local &callback); + virtual ~MethodCallBaton(); + + void run(); + v8::Local runSync(); + +protected: + v8::Local resultsToV8(JNIEnv *env); + virtual void Execute(); + virtual void WorkComplete(); + virtual void ExecuteInternal(JNIEnv *env) = 0; + static jmethodID getMethodInvokeMethodId(JNIEnv *env); + + Java *m_java; + jthrowable m_error; + std::string m_errorString; + jarray m_args; + jobject m_result; + jobject m_method; + +private: + static jmethodID m_methodInvokeMethodId; +}; + +class InstanceMethodCallBaton : public MethodCallBaton { +public: + InstanceMethodCallBaton(Java *java, JavaObject *obj, jobject method, jarray args, v8::Local &callback); + virtual ~InstanceMethodCallBaton(); + +protected: + virtual void ExecuteInternal(JNIEnv *env); + + JavaObject *m_javaObject; +}; + +class NewInstanceBaton : public MethodCallBaton { +public: + NewInstanceBaton(Java *java, jclass clazz, jobject method, jarray args, v8::Local &callback); + virtual ~NewInstanceBaton(); + +protected: + virtual void ExecuteInternal(JNIEnv *env); + + jclass m_clazz; +}; + +class StaticMethodCallBaton : public MethodCallBaton { +public: + StaticMethodCallBaton(Java *java, jclass clazz, jobject method, jarray args, v8::Local &callback); + virtual ~StaticMethodCallBaton(); + +protected: + virtual void ExecuteInternal(JNIEnv *env); + + jclass m_clazz; +}; + +#endif diff --git a/src-cpp/nodeJavaBridge.cpp b/src-cpp/nodeJavaBridge.cpp new file mode 100644 index 00000000..55b6bb41 --- /dev/null +++ b/src-cpp/nodeJavaBridge.cpp @@ -0,0 +1,18 @@ + +#include "java.h" +#include "javaObject.h" + +extern "C" { +static void init(v8::Local target, v8::Local, void *) { + Java::Init(target); + JavaObject::Init(target); +} + +NODE_MODULE(nodejavabridge_bindings, init); +} + +#ifdef WIN32 + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } + +#endif diff --git a/src/node_NodeDynamicProxyClass.h b/src-cpp/node_NodeDynamicProxyClass.h similarity index 68% rename from src/node_NodeDynamicProxyClass.h rename to src-cpp/node_NodeDynamicProxyClass.h index 3b7b67cf..d201a5d0 100644 --- a/src/node_NodeDynamicProxyClass.h +++ b/src-cpp/node_NodeDynamicProxyClass.h @@ -12,8 +12,14 @@ extern "C" { * Method: callJs * Signature: (JLjava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; */ -JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs - (JNIEnv *, jobject, jlong, jobject, jobjectArray); +JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *, jobject, jlong, jobject, jobjectArray); + +/* + * Class: node_NodeDynamicProxyClass + * Method: unref + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *, jobject, jlong); #ifdef __cplusplus } diff --git a/src-cpp/utils.cpp b/src-cpp/utils.cpp new file mode 100644 index 00000000..8b3784c5 --- /dev/null +++ b/src-cpp/utils.cpp @@ -0,0 +1,1005 @@ +#include "utils.h" +#include "java.h" +#include "javaObject.h" +#include +#include +#include +#include + +#define MODIFIER_STATIC 9 + +jobject v8ToJava_javaObject(JNIEnv *env, v8::Local obj); +jobject v8ToJava_javaLong(JNIEnv *env, v8::Local obj); + +bool hasSetFailed(Nan::Maybe v) { + if (v.IsNothing()) { + return false; + } + return v.ToChecked() == false; +} + +void javaReflectionGetMethods(JNIEnv *env, jclass clazz, std::list *methods, bool includeStatic) { + jclass clazzclazz = env->FindClass("java/lang/Class"); + jmethodID clazz_getMethods = env->GetMethodID(clazzclazz, "getMethods", "()[Ljava/lang/reflect/Method;"); + jclass methodClazz = env->FindClass("java/lang/reflect/Method"); + jmethodID method_getModifiers = env->GetMethodID(methodClazz, "getModifiers", "()I"); + + jobjectArray methodObjects = (jobjectArray)env->CallObjectMethod(clazz, clazz_getMethods); + checkJavaException(env); + jsize methodCount = env->GetArrayLength(methodObjects); + for (jsize i = 0; i < methodCount; i++) { + jobject method = env->GetObjectArrayElement(methodObjects, i); + jint methodModifiers = env->CallIntMethod(method, method_getModifiers); + assertNoException(env); + if (!includeStatic && (methodModifiers & MODIFIER_STATIC) == MODIFIER_STATIC) { + continue; + } + methods->push_back(method); + } +} + +void javaReflectionGetConstructors(JNIEnv *env, jclass clazz, std::list *methods) { + jclass clazzclazz = env->FindClass("java/lang/Class"); + jmethodID clazz_getConstructors = + env->GetMethodID(clazzclazz, "getConstructors", "()[Ljava/lang/reflect/Constructor;"); + + jobjectArray constructorObjects = (jobjectArray)env->CallObjectMethod(clazz, clazz_getConstructors); + checkJavaException(env); + jsize constructorCount = env->GetArrayLength(constructorObjects); + for (jsize i = 0; i < constructorCount; i++) { + jobject constructor = env->GetObjectArrayElement(constructorObjects, i); + methods->push_back(constructor); + } +} + +void javaReflectionGetFields(JNIEnv *env, jclass clazz, std::list *fields) { + jclass clazzclazz = env->FindClass("java/lang/Class"); + jmethodID clazz_getFields = env->GetMethodID(clazzclazz, "getFields", "()[Ljava/lang/reflect/Field;"); + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_getModifiers = env->GetMethodID(fieldClazz, "getModifiers", "()I"); + + jobjectArray fieldObjects = (jobjectArray)env->CallObjectMethod(clazz, clazz_getFields); + assertNoException(env); + jsize fieldCount = env->GetArrayLength(fieldObjects); + for (jsize i = 0; i < fieldCount; i++) { + jobject field = env->GetObjectArrayElement(fieldObjects, i); + jint fieldModifiers = env->CallIntMethod(field, field_getModifiers); + checkJavaException(env); + if ((fieldModifiers & MODIFIER_STATIC) == MODIFIER_STATIC) { + continue; + } + fields->push_back(field); + } +} + +std::string javaToString(JNIEnv *env, jstring str) { + jclass objClazz = env->GetObjectClass(str); + jmethodID methodId = env->GetMethodID(objClazz, "getBytes", "(Ljava/lang/String;)[B"); + + jstring charsetName = env->NewStringUTF("UTF-8"); + jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, methodId, charsetName); + env->DeleteLocalRef(charsetName); + + jbyte *pBytes = env->GetByteArrayElements(stringJbytes, NULL); + + const jsize length = env->GetArrayLength(stringJbytes); + std::string results((const char *)pBytes, length); + + env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); + env->DeleteLocalRef(stringJbytes); + + return results; +} + +std::string javaArrayToString(JNIEnv *env, jobjectArray arr) { + if (arr == NULL) { + return "(null)"; + } + + std::ostringstream result; + result << "["; + jsize count = env->GetArrayLength(arr); + for (jsize i = 0; i < count; i++) { + if (i != 0) { + result << ", "; + } + jobject obj = env->GetObjectArrayElement(arr, i); + result << javaObjectToString(env, obj); + } + result << "]"; + return result.str(); +} + +std::string javaObjectToString(JNIEnv *env, jobject obj) { + if (obj == NULL) { + return "(null)"; + } + jclass objClazz = env->GetObjectClass(obj); + jmethodID methodId = env->GetMethodID(objClazz, "toString", "()Ljava/lang/String;"); + jstring result = (jstring)env->CallObjectMethod(obj, methodId); + assertNoException(env); + return javaToString(env, result); +} + +std::string javaMethodCallToString(JNIEnv *env, jobject obj, jmethodID methodId, jarray args) { + char temp[100]; + + std::ostringstream result; + sprintf(temp, "%p", env); + result << temp; + result << ": "; + result << javaObjectToString(env, obj); + result << ": "; + sprintf(temp, "%p", methodId); + result << temp; + result << ": ("; + jsize arraySize = env->GetArrayLength(args); + for (int i = 0; i < arraySize; i++) { + if (i != 0) { + result << ", "; + } + jobject arg = env->GetObjectArrayElement((jobjectArray)args, i); + result << javaObjectToString(env, arg); + } + result << ")"; + + return result.str(); +} + +JNIEnv *javaGetEnv(JavaVM *jvm, jobject classLoader) { + JNIEnv *env = NULL; + int ret = jvm->GetEnv((void **)&env, JNI_BEST_VERSION); + + if (ret == JNI_EDETACHED) { + JavaVMAttachArgs attachArgs; + attachArgs.version = JNI_BEST_VERSION; + attachArgs.name = NULL; + attachArgs.group = NULL; + jvm->AttachCurrentThread((void **)&env, &attachArgs); + + jclass threadClazz = env->FindClass("java/lang/Thread"); + jmethodID thread_currentThread = env->GetStaticMethodID(threadClazz, "currentThread", "()Ljava/lang/Thread;"); + jmethodID thread_setContextClassLoader = + env->GetMethodID(threadClazz, "setContextClassLoader", "(Ljava/lang/ClassLoader;)V"); + jobject currentThread = env->CallStaticObjectMethod(threadClazz, thread_currentThread); + checkJavaException(env); + env->CallObjectMethod(currentThread, thread_setContextClassLoader, classLoader); + assertNoException(env); + + env->DeleteLocalRef(threadClazz); + env->DeleteLocalRef(currentThread); + } + + return env; +} + +jobject getSystemClassLoader(JNIEnv *env) { + jclass threadClazz = env->FindClass("java/lang/Thread"); + jmethodID thread_currentThread = env->GetStaticMethodID(threadClazz, "currentThread", "()Ljava/lang/Thread;"); + jmethodID thread_getContextClassLoader = + env->GetMethodID(threadClazz, "getContextClassLoader", "()Ljava/lang/ClassLoader;"); + jobject currentThread = env->CallStaticObjectMethod(threadClazz, thread_currentThread); + checkJavaException(env); + jobject result = env->CallObjectMethod(currentThread, thread_getContextClassLoader); + checkJavaException(env); + return result; +} + +jvalueType javaGetType(JNIEnv *env, jclass type) { + jclass clazzClazz = env->FindClass("java/lang/Class"); + jmethodID class_isArray = env->GetMethodID(clazzClazz, "isArray", "()Z"); + + jboolean isArray = env->CallBooleanMethod(type, class_isArray); + assertNoException(env); + if (isArray) { + return TYPE_ARRAY; + } else { + // TODO: has to be a better way + std::string str = javaObjectToString(env, type); + const char *typeStr = str.c_str(); + // printf("javaGetType: %s\n", typeStr); + if (strcmp(typeStr, "void") == 0) { + return TYPE_VOID; + } else if (strcmp(typeStr, "char") == 0 || strcmp(typeStr, "class java.lang.Character") == 0) { + return TYPE_CHAR; + } else if (strcmp(typeStr, "int") == 0 || strcmp(typeStr, "class java.lang.Integer") == 0) { + return TYPE_INT; + } else if (strcmp(typeStr, "double") == 0 || strcmp(typeStr, "class java.lang.Double") == 0) { + return TYPE_DOUBLE; + } else if (strcmp(typeStr, "float") == 0 || strcmp(typeStr, "class java.lang.Float") == 0) { + return TYPE_FLOAT; + } else if (strcmp(typeStr, "long") == 0 || strcmp(typeStr, "class java.lang.Long") == 0) { + return TYPE_LONG; + } else if (strcmp(typeStr, "boolean") == 0 || strcmp(typeStr, "class java.lang.Boolean") == 0) { + return TYPE_BOOLEAN; + } else if (strcmp(typeStr, "short") == 0 || strcmp(typeStr, "class java.lang.Short") == 0) { + return TYPE_SHORT; + } else if (strcmp(typeStr, "byte") == 0 || strcmp(typeStr, "class java.lang.Byte") == 0) { + return TYPE_BYTE; + } else if (strcmp(typeStr, "class java.lang.String") == 0) { + return TYPE_STRING; + } + return TYPE_OBJECT; + } +} + +jclass javaFindClass(JNIEnv *env, const std::string &className) { + std::string searchClassName = className; + std::replace(searchClassName.begin(), searchClassName.end(), '.', '/'); + + // Alternate find class trying to fix Class.forName + // jclass threadClazz = env->FindClass("java/lang/Thread"); + // jmethodID thread_getCurrentThread = env->GetStaticMethodID(threadClazz, "currentThread", "()Ljava/lang/Thread;"); + // jmethodID thread_getContextClassLoader = env->GetMethodID(threadClazz, "getContextClassLoader", + // "()Ljava/lang/ClassLoader;"); + // + // jclass classLoaderClazz = env->FindClass("java/lang/ClassLoader"); + // jmethodID classLoader_loadClass = env->GetMethodID(classLoaderClazz, "loadClass", + // "(Ljava/lang/String;)Ljava/lang/Class;"); + // + // jobject currentThread = env->CallObjectMethod(threadClazz, thread_getCurrentThread); + // jobject classLoader = env->CallObjectMethod(currentThread, thread_getContextClassLoader); + // jstring searchClassNameJava = env->NewStringUTF(className.c_str()); + // jclass clazz = (jclass)env->CallObjectMethod(classLoader, classLoader_loadClass, searchClassNameJava); + + jclass clazz = env->FindClass(searchClassName.c_str()); + return clazz; +} + +jobject javaFindField(JNIEnv *env, jclass clazz, const std::string &fieldName) { + jobject result = NULL; + jclass clazzclazz = env->GetObjectClass(clazz); + jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); + jmethodID field_getName = env->GetMethodID(fieldClazz, "getName", "()Ljava/lang/String;"); + jmethodID class_getFields = env->GetMethodID(clazzclazz, "getFields", "()[Ljava/lang/reflect/Field;"); + jobjectArray fieldObjects = (jobjectArray)env->CallObjectMethod(clazz, class_getFields); + checkJavaException(env); + + jsize fieldCount = env->GetArrayLength(fieldObjects); + for (jsize i = 0; i < fieldCount; i++) { + jobject field = env->GetObjectArrayElement(fieldObjects, i); + jstring fieldNameJava = (jstring)env->CallObjectMethod(field, field_getName); + checkJavaException(env); + + std::string itFieldName = javaToString(env, fieldNameJava); + if (strcmp(itFieldName.c_str(), fieldName.c_str()) == 0) { + result = field; + break; + } + } + + return result; +} + +const std::string kObject("java/lang/Object"); +const std::string kString("java/lang/String"); +const std::string kInteger("java/lang/Integer"); +const std::string kNumber("java/lang/Number"); +const std::string kLong("java/lang/Long"); +const std::string kDouble("java/lang/Double"); +const std::string kBoolean("java/lang/Boolean"); + +static std::string getArrayElementType(v8::Local array, uint32_t arraySize) { + std::set types; + + if (arraySize == 0) { + return kObject; + } + + for (uint32_t i = 0; i < arraySize; i++) { + v8::Local arg = array->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); + if (arg->IsArray()) { + return kObject; // We can exit as soon as we know java/lang/Object is required. + } else if (arg->IsString()) { + types.insert(kString); + } else if (arg->IsInt32() || arg->IsUint32()) { + types.insert(kInteger); + } else if (arg->IsNumber()) { + types.insert(kDouble); + } else if (arg->IsBoolean()) { + types.insert(kBoolean); + } else if (arg->IsObject()) { + v8::Local obj = v8::Local::Cast(arg); + v8::Local isJavaLong = + GetHiddenValue(obj, Nan::New(V8_HIDDEN_MARKER_JAVA_LONG).ToLocalChecked()); + if (!isJavaLong.IsEmpty() && isJavaLong->IsBoolean()) { + types.insert(kLong); + } else { + return kObject; // We can exit as soon as we know java/lang/Object is required. + } + } + } + + if (types.size() == 1) { + return *(types.begin()); + } + + assert(types.size() >= 1); + assert(types.find(kObject) == types.end()); + + // We have an array with two or more types. All types can be converted to Object, but there is one other + // case we support, which is when all the types are numeric types. + // We currently have only two non-numeric types. If neither is present in the set, the rest must be numeric. + if (types.find(kString) == types.end() && types.find(kBoolean) == types.end()) { + return kNumber; + } + + return kObject; +} + +jobject v8ToJava(JNIEnv *env, v8::Local arg) { + if (arg.IsEmpty() || arg->IsNull() || arg->IsUndefined()) { + return NULL; + } + + if (arg->IsArray()) { + v8::Local array = v8::Local::Cast(arg); + uint32_t arraySize = array->Length(); + std::string arrayType = getArrayElementType(array, arraySize); + jclass objectClazz = env->FindClass(arrayType.c_str()); + jobjectArray result = env->NewObjectArray(arraySize, objectClazz, NULL); + for (uint32_t i = 0; i < arraySize; i++) { + jobject val = v8ToJava(env, array->Get(Nan::GetCurrentContext(), i).ToLocalChecked()); + env->SetObjectArrayElement(result, i, val); + } + return result; + } + + if (arg->IsString()) { +#if NODE_MAJOR_VERSION > 7 + v8::String::Value val(v8::Isolate::GetCurrent(), arg->ToString(Nan::GetCurrentContext()).ToLocalChecked()); +#else + v8::String::Value val(arg->ToString(Nan::GetCurrentContext()).ToLocalChecked()); +#endif + return env->NewString(*val, val.length()); + } + + if (arg->IsInt32() || arg->IsUint32()) { + jint val = Nan::To(arg).FromJust(); + jclass clazz = env->FindClass("java/lang/Integer"); + jmethodID constructor = env->GetMethodID(clazz, "", "(I)V"); + return env->NewObject(clazz, constructor, val); + } + + if (arg->IsNumber()) { + jdouble val = Nan::To(arg).FromJust(); + jclass clazz = env->FindClass("java/lang/Double"); + jmethodID constructor = env->GetMethodID(clazz, "", "(D)V"); + return env->NewObject(clazz, constructor, val); + } + + if (arg->IsBoolean()) { + jboolean val = Nan::To(arg).FromJust(); + jclass clazz = env->FindClass("java/lang/Boolean"); + jmethodID constructor = env->GetMethodID(clazz, "", "(Z)V"); + return env->NewObject(clazz, constructor, val); + } + + if (arg->IsObject()) { + v8::Local obj = v8::Local::Cast(arg); + + v8::Local isJavaObject = + GetHiddenValue(obj, Nan::New(V8_HIDDEN_MARKER_JAVA_OBJECT).ToLocalChecked()); + if (!isJavaObject.IsEmpty() && isJavaObject->IsBoolean()) { + return v8ToJava_javaObject(env, obj); + } + + v8::Local isJavaLong = + GetHiddenValue(obj, Nan::New(V8_HIDDEN_MARKER_JAVA_LONG).ToLocalChecked()); + if (!isJavaLong.IsEmpty() && isJavaLong->IsBoolean()) { + return v8ToJava_javaLong(env, obj); + } + } + + // TODO: handle other arg types. Don't print here, see instanceof-test#non-java object + // v8::String::AsciiValue typeStr(arg); + // printf("v8ToJava: Unhandled type: %s\n", *typeStr); + return NULL; +} + +jobject v8ToJava_javaObject(JNIEnv *env, v8::Local obj) { + JavaObject *javaObject = Nan::ObjectWrap::Unwrap(obj); + return javaObject->getObject(); +} + +void checkJavaException(JNIEnv *env) { + if (env->ExceptionCheck()) { + jthrowable ex = env->ExceptionOccurred(); + env->ExceptionClear(); + + std::string exString = javaExceptionToString(env, ex); + printf("%s\n", exString.c_str()); + assert(false); + } +} + +jobject v8ToJava_javaLong(JNIEnv *env, v8::Local obj) { + jobject longValue = v8ToJava( + env, obj->Get(Nan::GetCurrentContext(), Nan::New("longValue").ToLocalChecked()).ToLocalChecked()); + jclass longClazz = env->FindClass("java/lang/Long"); + jmethodID long_constructor = env->GetMethodID(longClazz, "", "(Ljava/lang/String;)V"); + jobject jobj = env->NewObject(longClazz, long_constructor, longValue); + return jobj; +} + +jobjectArray v8ToJava(JNIEnv *env, Nan::NAN_METHOD_ARGS_TYPE args, int start, int end) { + jclass clazz = env->FindClass("java/lang/Object"); + jobjectArray results = env->NewObjectArray(end - start, clazz, NULL); + + for (int i = start; i < end; i++) { + jobject val = v8ToJava(env, args[i]); + env->SetObjectArrayElement(results, i - start, val); + } + + return results; +} + +std::string javaExceptionToString(JNIEnv *env, jthrowable ex) { + jclass stringWriterClazz = env->FindClass("java/io/StringWriter"); + jmethodID stringWriter_constructor = env->GetMethodID(stringWriterClazz, "", "()V"); + jmethodID stringWriter_toString = env->GetMethodID(stringWriterClazz, "toString", "()Ljava/lang/String;"); + jobject stringWriter = env->NewObject(stringWriterClazz, stringWriter_constructor); + + jclass printWriterClazz = env->FindClass("java/io/PrintWriter"); + jmethodID printWriter_constructor = env->GetMethodID(printWriterClazz, "", "(Ljava/io/Writer;)V"); + jobject printWriter = env->NewObject(printWriterClazz, printWriter_constructor, stringWriter); + + jclass throwableClazz = env->FindClass("java/lang/Throwable"); + jmethodID throwable_printStackTrace = env->GetMethodID(throwableClazz, "printStackTrace", "(Ljava/io/PrintWriter;)V"); + env->CallObjectMethod(ex, throwable_printStackTrace, printWriter); + checkJavaException(env); + jstring strObj = (jstring)env->CallObjectMethod(stringWriter, stringWriter_toString); + checkJavaException(env); + + return javaToString(env, strObj); +} + +v8::Local javaExceptionToV8(Java *java, JNIEnv *env, jthrowable ex, const std::string &alternateMessage) { + std::ostringstream msg; + msg << alternateMessage; + + if (ex) { + msg << "\n" << javaExceptionToString(env, ex); + + v8::Local v8ex = v8::Exception::Error(Nan::New(msg.str().c_str()).ToLocalChecked()); + if (hasSetFailed(((v8::Object *)*v8ex) + ->Set(Nan::GetCurrentContext(), Nan::New("cause").ToLocalChecked(), + javaToV8(java, env, ex)))) { + return v8::Exception::Error(Nan::New("could not set cause").ToLocalChecked()); + } + return v8ex; + } + + return v8::Exception::Error(Nan::New(msg.str().c_str()).ToLocalChecked()); +} + +v8::Local javaExceptionToV8(Java *java, JNIEnv *env, const std::string &alternateMessage) { + jthrowable ex = env->ExceptionOccurred(); + env->ExceptionClear(); + return javaExceptionToV8(java, env, ex, alternateMessage); +} + +jvalueType javaGetArrayComponentType(JNIEnv *env, jobjectArray array) { + jclass objectClazz = env->FindClass("java/lang/Object"); + jclass clazzclazz = env->FindClass("java/lang/Class"); + + jmethodID object_getClass = env->GetMethodID(objectClazz, "getClass", "()Ljava/lang/Class;"); + jobject arrayClass = env->CallObjectMethod(array, object_getClass); + assertNoException(env); + + jmethodID class_getComponentType = env->GetMethodID(clazzclazz, "getComponentType", "()Ljava/lang/Class;"); + jobject arrayComponentTypeClass = env->CallObjectMethod(arrayClass, class_getComponentType); + checkJavaException(env); + + jvalueType arrayComponentType = javaGetType(env, (jclass)arrayComponentTypeClass); + return arrayComponentType; +} + +#if NODE_VERSION_AT_LEAST(13, 0, 0) +v8::Local newArrayBuffer(void *elems, size_t length) { + v8::Local ab = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), length); + memcpy(ab->GetBackingStore()->Data(), elems, length); + return ab; +} +#elif NODE_VERSION_AT_LEAST(4, 0, 0) +v8::Local newArrayBuffer(void *elems, size_t length) { + v8::Local ab = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), length); + memcpy(ab->GetContents().Data(), elems, length); + return ab; +} +#endif + +v8::Local javaCharToV8String(jchar c) { +#if ((NODE_MAJOR_VERSION == 0) && (NODE_MINOR_VERSION <= 10)) + return v8::String::New(&c, 1); +#elif ((NODE_MAJOR_VERSION == 0) && (NODE_MINOR_VERSION <= 12)) + return v8::String::NewFromTwoByte(v8::Isolate::GetCurrent(), &c, v8::String::kNormalString, 1); +#else + return v8::String::NewFromTwoByte(v8::Isolate::GetCurrent(), &c, v8::NewStringType::kNormal, 1).ToLocalChecked(); +#endif +} + +v8::Local javaArrayToV8(Java *java, JNIEnv *env, jobjectArray objArray) { + if (objArray == NULL) { + return Nan::Null(); + } + + jvalueType arrayComponentType = javaGetArrayComponentType(env, objArray); + // printf("javaArrayToV8: %d %s\n", arrayComponentType, javaObjectToString(env, objArray).c_str()); + + jsize arraySize = env->GetArrayLength(objArray); + // printf("array size: %d\n", arraySize); + + v8::Local result = Nan::New(arraySize); + // http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html + switch (arrayComponentType) { + case TYPE_CHAR: { + jchar *elems = env->GetCharArrayElements((jcharArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize * 2; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseCharArrayElements((jcharArray)objArray, elems, 0); + return v8::Uint16Array::New(ab, 0, arraySize); +#else + jchar str; + for (jsize i = 0; i < arraySize; i++) { + str = elems[i]; + result->Set(i, javaCharToV8String(str)); + } + env->ReleaseCharArrayElements((jcharArray)objArray, elems, 0); +#endif + } break; + + case TYPE_INT: { + jint *elems = env->GetIntArrayElements((jintArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize * 4; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseIntArrayElements((jintArray)objArray, elems, 0); + return v8::Int32Array::New(ab, 0, arraySize); +#else + for (jsize i = 0; i < arraySize; i++) { + result->Set(i, Nan::New(elems[i])); + } + env->ReleaseIntArrayElements((jintArray)objArray, elems, 0); +#endif + } break; + + case TYPE_BYTE: { + jbyte *elems = env->GetByteArrayElements((jbyteArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseByteArrayElements((jbyteArray)objArray, elems, 0); + return v8::Int8Array::New(ab, 0, arraySize); +#else + for (jsize i = 0; i < arraySize; i++) { + result->Set(i, Nan::New(elems[i])); + } + env->ReleaseByteArrayElements((jbyteArray)objArray, elems, 0); +#endif + } break; + + case TYPE_BOOLEAN: { + jboolean *elems = env->GetBooleanArrayElements((jbooleanArray)objArray, 0); + for (jsize i = 0; i < arraySize; i++) { + if (hasSetFailed(result->Set(Nan::GetCurrentContext(), i, Nan::New(elems[i])))) { + return v8::Exception::Error(Nan::New("set array element failed").ToLocalChecked()); + } + } + env->ReleaseBooleanArrayElements((jbooleanArray)objArray, elems, 0); + } break; + + case TYPE_SHORT: { + jshort *elems = env->GetShortArrayElements((jshortArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize * 2; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseShortArrayElements((jshortArray)objArray, elems, 0); + return v8::Int16Array::New(ab, 0, arraySize); +#else + for (jsize i = 0; i < arraySize; i++) { + result->Set(i, Nan::New(elems[i])); + } + env->ReleaseShortArrayElements((jshortArray)objArray, elems, 0); +#endif + } break; + + case TYPE_DOUBLE: { + jdouble *elems = env->GetDoubleArrayElements((jdoubleArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize * 8; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseDoubleArrayElements((jdoubleArray)objArray, elems, 0); + return v8::Float64Array::New(ab, 0, arraySize); +#else + for (jsize i = 0; i < arraySize; i++) { + result->Set(i, Nan::New(elems[i])); + } + env->ReleaseDoubleArrayElements((jdoubleArray)objArray, elems, 0); +#endif + } break; + + case TYPE_FLOAT: { + jfloat *elems = env->GetFloatArrayElements((jfloatArray)objArray, 0); +#if (NODE_VERSION_AT_LEAST(4, 0, 0)) + size_t byteLength = arraySize * 4; + v8::Local ab = newArrayBuffer(elems, byteLength); + env->ReleaseFloatArrayElements((jfloatArray)objArray, elems, 0); + return v8::Float32Array::New(ab, 0, arraySize); +#else + for (jsize i = 0; i < arraySize; i++) { + result->Set(i, Nan::New(elems[i])); + } + env->ReleaseFloatArrayElements((jfloatArray)objArray, elems, 0); +#endif + } break; + + case TYPE_LONG: { + jlong *elems = env->GetLongArrayElements((jlongArray)objArray, 0); + for (jsize i = 0; i < arraySize; i++) { + jobject obj = longToJavaLongObj(env, elems[i]); + if (hasSetFailed(result->Set(Nan::GetCurrentContext(), i, JavaObject::New(java, obj)))) { + return v8::Exception::Error(Nan::New("could not set array element").ToLocalChecked()); + } + } + env->ReleaseLongArrayElements((jlongArray)objArray, elems, 0); + } break; + + default: + for (jsize i = 0; i < arraySize; i++) { + jobject obj = env->GetObjectArrayElement(objArray, i); + v8::Local item = javaToV8(java, env, obj); + if (hasSetFailed(result->Set(Nan::GetCurrentContext(), i, item))) { + return v8::Exception::Error(Nan::New("could not set array element").ToLocalChecked()); + } + } + break; + } + + return result; +} + +v8::Local javaToV8(Java *java, JNIEnv *env, jobject obj) { return javaToV8(java, env, obj, NULL); } + +v8::Local javaToV8(Java *java, JNIEnv *env, jobject obj, DynamicProxyData *dynamicProxyData) { + if (obj == NULL) { + return Nan::Null(); + } + + jclass objClazz = env->GetObjectClass(obj); + jvalueType resultType = javaGetType(env, objClazz); + + // printf("javaToV8: %d %s\n", resultType, javaObjectToString(env, obj).c_str()); + + switch (resultType) { + case TYPE_ARRAY: { + v8::Local result = javaArrayToV8(java, env, (jobjectArray)obj); + return result; + } + case TYPE_VOID: + return Nan::Undefined(); + case TYPE_CHAR: { + jclass charClazz = env->FindClass("java/lang/Character"); + jmethodID char_charValue = env->GetMethodID(charClazz, "charValue", "()C"); + jchar c = env->CallCharMethod(obj, char_charValue); + checkJavaException(env); + return javaCharToV8String(c); + } + case TYPE_BOOLEAN: { + jclass booleanClazz = env->FindClass("java/lang/Boolean"); + jmethodID boolean_booleanValue = env->GetMethodID(booleanClazz, "booleanValue", "()Z"); + bool result = env->CallBooleanMethod(obj, boolean_booleanValue); + assertNoException(env); + return Nan::New(result); + } + case TYPE_BYTE: { + jclass byteClazz = env->FindClass("java/lang/Byte"); + jmethodID byte_byteValue = env->GetMethodID(byteClazz, "byteValue", "()B"); + jbyte result = env->CallByteMethod(obj, byte_byteValue); + checkJavaException(env); + return Nan::New(result); + } + case TYPE_LONG: { + jclass longClazz = env->FindClass("java/lang/Long"); + jmethodID long_longValue = env->GetMethodID(longClazz, "longValue", "()J"); + jlong result = env->CallLongMethod(obj, long_longValue); + checkJavaException(env); + std::string strValue = javaObjectToString(env, obj); + v8::Local v8Result = Nan::New((double)result); + v8::NumberObject *v8ResultNumberObject = v8::NumberObject::Cast(*v8Result); + if (hasSetFailed(v8ResultNumberObject->Set(Nan::GetCurrentContext(), + Nan::New("longValue").ToLocalChecked(), + Nan::New(strValue.c_str()).ToLocalChecked()))) { + return v8::Exception::Error(Nan::New("could not set longValue").ToLocalChecked()); + } + SetHiddenValue(v8ResultNumberObject, Nan::New(V8_HIDDEN_MARKER_JAVA_LONG).ToLocalChecked(), + Nan::New(true)); + return v8Result; + } + case TYPE_INT: { + jclass integerClazz = env->FindClass("java/lang/Integer"); + jmethodID integer_intValue = env->GetMethodID(integerClazz, "intValue", "()I"); + jint result = env->CallIntMethod(obj, integer_intValue); + checkJavaException(env); + // <-- use the Int32 factory, not the generic Integer one + return Nan::New(static_cast(result)); + } + case TYPE_SHORT: { + jclass shortClazz = env->FindClass("java/lang/Short"); + jmethodID short_shortValue = env->GetMethodID(shortClazz, "shortValue", "()S"); + jshort result = env->CallShortMethod(obj, short_shortValue); + assertNoException(env); + return Nan::New(result); + } + case TYPE_DOUBLE: { + jclass doubleClazz = env->FindClass("java/lang/Double"); + jmethodID double_doubleValue = env->GetMethodID(doubleClazz, "doubleValue", "()D"); + jdouble result = env->CallDoubleMethod(obj, double_doubleValue); + checkJavaException(env); + return Nan::New(result); + } + case TYPE_FLOAT: { + jclass floatClazz = env->FindClass("java/lang/Float"); + jmethodID float_floatValue = env->GetMethodID(floatClazz, "floatValue", "()F"); + jfloat result = env->CallFloatMethod(obj, float_floatValue); + assertNoException(env); + return Nan::New(result); + } + case TYPE_STRING: { + std::string str = javaObjectToString(env, obj); + return Nan::New(str.c_str(), str.length()).ToLocalChecked(); + } + case TYPE_OBJECT: + if (dynamicProxyData != NULL) { + return JavaProxyObject::New(java, obj, dynamicProxyData); + } + return JavaObject::New(java, obj); + default: + printf("javaToV8: unhandled type: 0x%03x\n", resultType); + return JavaObject::New(java, obj); + } + + return Nan::Undefined(); +} + +jobjectArray javaObjectArrayToClasses(JNIEnv *env, jobjectArray objs) { + jclass clazzClazz = env->FindClass("java/lang/Class"); + jsize objsLength = env->GetArrayLength(objs); + jobjectArray results = env->NewObjectArray(objsLength, clazzClazz, NULL); + for (jsize i = 0; i < objsLength; i++) { + jobject elem = env->GetObjectArrayElement(objs, i); + if (elem == NULL) { + env->SetObjectArrayElement(results, i, NULL); + } else { + jclass objClazz = env->GetObjectClass(elem); + env->SetObjectArrayElement(results, i, objClazz); + } + } + + return results; +} + +jobject javaFindMethod(JNIEnv *env, jclass clazz, const std::string &methodName, jobjectArray methodArgs) { + std::string::size_type parenLoc = methodName.find("("); + if (parenLoc != std::string::npos) { + jobject method = NULL; + + std::string methodSig = methodName.substr(parenLoc); + std::string methodRealName = methodName.substr(0, parenLoc); + jmethodID methodID = env->GetStaticMethodID(clazz, methodRealName.c_str(), methodSig.c_str()); + env->ExceptionClear(); // If GetStaticMethodID can't find the method it throws an exception and we need to just + // return NULL + if (methodID != 0) { + method = env->ToReflectedMethod(clazz, methodID, true); + } else { + methodID = env->GetMethodID(clazz, methodRealName.c_str(), methodSig.c_str()); + env->ExceptionClear(); // If GetMethodID can't find the method it throws an exception and we need to just return + // NULL + if (methodID != 0) { + method = env->ToReflectedMethod(clazz, methodID, true); + } + } + + // cast arguments + if (method != NULL) { + javaCastArguments(env, methodArgs, method); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + method = NULL; + } + } + + return method; + } else { + jclass methodUtilsClazz = env->FindClass("nodejava/org/apache/commons/lang3/reflect/MethodUtils"); + jmethodID methodUtils_getMatchingAccessibleMethod = + env->GetStaticMethodID(methodUtilsClazz, "getMatchingAccessibleMethod", + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); + const char *methodNameCStr = methodName.c_str(); + jstring methodNameJavaStr = env->NewStringUTF(methodNameCStr); + jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs); + jobject method = env->CallStaticObjectMethod(methodUtilsClazz, methodUtils_getMatchingAccessibleMethod, clazz, + methodNameJavaStr, methodArgClasses); + checkJavaException(env); + return method; + } +} + +void javaCastArguments(JNIEnv *env, jobjectArray methodArgs, jobject method) { + jclass castingUtilsClazz = env->FindClass("node/CastingUtils"); + jmethodID castingUtilsClazz_cast = + env->GetStaticMethodID(castingUtilsClazz, "cast", "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)V"); + env->CallStaticObjectMethod(castingUtilsClazz, castingUtilsClazz_cast, method, methodArgs); +} + +jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs) { + jclass constructorUtilsClazz = env->FindClass("nodejava/org/apache/commons/lang3/reflect/ConstructorUtils"); + jmethodID constructorUtils_getMatchingAccessibleConstructor = + env->GetStaticMethodID(constructorUtilsClazz, "getMatchingAccessibleConstructor", + "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"); + jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs); + jobject method = env->CallStaticObjectMethod(constructorUtilsClazz, constructorUtils_getMatchingAccessibleConstructor, + clazz, methodArgClasses); + assertNoException(env); + return method; +} + +jobject longToJavaLongObj(JNIEnv *env, jlong val) { + jclass longClass = env->FindClass("java/lang/Long"); + jmethodID constructor = env->GetMethodID(longClass, "", "(J)V"); + jobject result = env->NewObject(longClass, constructor, val); + return result; +} + +int dynamicProxyDataVerify(DynamicProxyData *data) { + if (data->markerStart == DYNAMIC_PROXY_DATA_MARKER_START && data->markerEnd == DYNAMIC_PROXY_DATA_MARKER_END) { + return 1; + } + + printf("*** ERROR: Lost reference to the dynamic proxy. You must maintain a reference in javascript land using ref() " + "and unref(). (%p) ***\n", + data); + return 0; +} + +std::string methodNotFoundToString(JNIEnv *env, jclass clazz, const std::string &methodNameSig, bool constructor, + Nan::NAN_METHOD_ARGS_TYPE args, int argStart, int argEnd) { + std::ostringstream startOfMessage; + std::ostringstream msg; + std::string methodName = methodNameSig.substr(0, methodNameSig.find('(')); + + jclass classClazz = env->FindClass("java/lang/Class"); + jmethodID class_getName = env->GetMethodID(classClazz, "getName", "()Ljava/lang/String;"); + + if (methodName != methodNameSig) { + startOfMessage << "Could not find method for signature \"" << methodNameSig.c_str() << "\" and arguments \"("; + } else { + startOfMessage << "Could not find method \"" << methodName.c_str() << "("; + } + + for (int i = argStart; i < argEnd; i++) { + jobject val = v8ToJava(env, args[i]); + if (i != argStart) { + startOfMessage << ", "; + } + if (val == NULL) { + startOfMessage << "(null)"; + } else { + jclass argClass = env->GetObjectClass(val); + jstring argClassNameJava = (jstring)env->CallObjectMethod(argClass, class_getName); + checkJavaException(env); + std::string argClassName = javaToString(env, argClassNameJava); + startOfMessage << argClassName; + } + } + + startOfMessage << ")\" on class \"" << javaObjectToString(env, clazz).c_str() << "\"."; + + msg << startOfMessage.str() << " Possible matches:\n"; + + jclass memberClazz = env->FindClass("java/lang/reflect/Member"); + jmethodID member_getName = env->GetMethodID(memberClazz, "getName", "()Ljava/lang/String;"); + + std::list methods; + if (constructor) { + javaReflectionGetConstructors(env, clazz, &methods); + } else { + javaReflectionGetMethods(env, clazz, &methods, true); + } + + int count = 0; + for (std::list::iterator it = methods.begin(); it != methods.end(); ++it) { + jstring methodNameTestJava = (jstring)env->CallObjectMethod(*it, member_getName); + assertNoException(env); + std::string methodNameTest = javaToString(env, methodNameTestJava); + if (methodNameTest == methodName) { + msg << " " << javaObjectToString(env, *it).c_str() << "\n"; + count++; + } + } + + if (count == 0) { + std::ostringstream noMethodsMsg; + noMethodsMsg << startOfMessage.str() << " No methods with that name."; + return noMethodsMsg.str(); + } + + return msg.str(); +} + +void unref(DynamicProxyData *dynamicProxyData) { + if (!dynamicProxyDataVerify(dynamicProxyData)) { + return; + } + dynamicProxyData->jsObject.Reset(); + dynamicProxyData->functions.Reset(); + dynamicProxyData->markerStart = 0; + dynamicProxyData->markerEnd = 0; + delete dynamicProxyData; +} + +jarray javaGetArgsForMethod(JNIEnv *env, jobject method, jarray args) { + jclass varArgsClazz = env->FindClass("node/VarArgs"); + jmethodID method_getVarArgs = env->GetStaticMethodID( + varArgsClazz, "getVarArgs", "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)[Ljava/lang/Object;"); + jarray result = (jarray)env->CallStaticObjectMethod(varArgsClazz, method_getVarArgs, method, args); + checkJavaException(env); + return result; +} + +jarray javaGetArgsForConstructor(JNIEnv *env, jobject method, jarray args) { + jclass varArgsClazz = env->FindClass("node/VarArgs"); + jmethodID method_getVarArgs = env->GetStaticMethodID( + varArgsClazz, "getVarArgs", "(Ljava/lang/reflect/Constructor;[Ljava/lang/Object;)[Ljava/lang/Object;"); + jarray result = (jarray)env->CallStaticObjectMethod(varArgsClazz, method_getVarArgs, method, args); + checkJavaException(env); + return result; +} + +#if (NODE_MODULE_VERSION > 48) +// The two methods below were copied from +// https://github.com/electron/electron?branch=master&filepath=atom/common/api/atom_api_v8_util.cc +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license. + +v8::Local GetHiddenValue(v8::Local object, v8::Local key) { + v8::Local context = v8::Isolate::GetCurrent()->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(v8::Isolate::GetCurrent(), key); + v8::Local value; + v8::Maybe result = object->HasPrivate(context, privateKey); + if (!(result.IsJust() && result.FromJust())) + return v8::Local(); + if (object->GetPrivate(context, privateKey).ToLocal(&value)) + return value; + return v8::Local(); +} + +void SetHiddenValue(v8::NumberObject *object, v8::Local key, v8::Local value) { + if (value.IsEmpty()) + return; + v8::Local context = v8::Isolate::GetCurrent()->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(v8::Isolate::GetCurrent(), key); + object->SetPrivate(context, privateKey, value); +} + +void SetHiddenValue(v8::Local object, v8::Local key, v8::Local value) { + if (value.IsEmpty()) + return; + v8::Local context = v8::Isolate::GetCurrent()->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(v8::Isolate::GetCurrent(), key); + object->SetPrivate(context, privateKey, value); +} +#else +v8::Local GetHiddenValue(v8::Local object, v8::Local key) { + return object->GetHiddenValue(key); +} + +void SetHiddenValue(v8::NumberObject *object, v8::Local key, v8::Local value) { + object->SetHiddenValue(key, value); +} + +void SetHiddenValue(v8::Local object, v8::Local key, v8::Local value) { + object->SetHiddenValue(key, value); +} +#endif diff --git a/src-cpp/utils.h b/src-cpp/utils.h new file mode 100644 index 00000000..bc7cc171 --- /dev/null +++ b/src-cpp/utils.h @@ -0,0 +1,175 @@ + +#ifndef _utils_h_ +#define _utils_h_ + +#define BUILDING_NODE_EXTENSION 1 +#include +#include +#include +#include +#include +#include +#include + +class Java; + +#define V8_HIDDEN_MARKER_JAVA_LONG "__isJavaLong" +#define V8_HIDDEN_MARKER_JAVA_OBJECT "__isJavaObject" + +#if NODE_MAJOR_VERSION >= 23 + #define RETURN_INTERCEPTED_YES return v8::Intercepted::kYes + #define RETURN_INTERCEPTED_NO return v8::Intercepted::kNo +#else + #define RETURN_INTERCEPTED_YES return + #define RETURN_INTERCEPTED_NO return +#endif + +typedef enum _jvalueType { + TYPE_VOID = 1, + TYPE_INT = 2, + TYPE_LONG = 3, + TYPE_OBJECT = 4, + TYPE_STRING = 5, + TYPE_BOOLEAN = 6, + TYPE_SHORT = 7, + TYPE_BYTE = 8, + TYPE_DOUBLE = 9, + TYPE_FLOAT = 10, + TYPE_ARRAY = 11, + TYPE_CHAR = 12 +} jvalueType; + +struct DynamicProxyData { + unsigned int markerStart; + Java *java; + std::string interfaceName; + Nan::Persistent functions; + Nan::Persistent jsObject; + unsigned int markerEnd; +}; + +struct DynamicProxyJsCallData { + DynamicProxyData *dynamicProxyData; + std::string methodName; + jobjectArray args; + jobject result; + std::string throwableClass; + std::string throwableMessage; + int done; +}; + +#define DYNAMIC_PROXY_DATA_MARKER_START 0x12345678 +#define DYNAMIC_PROXY_DATA_MARKER_END 0x87654321 + +int dynamicProxyDataVerify(DynamicProxyData *data); + +void javaReflectionGetMethods(JNIEnv *env, jclass clazz, std::list *methods, bool includeStatic); +void javaReflectionGetConstructors(JNIEnv *env, jclass clazz, std::list *methods); +void javaReflectionGetFields(JNIEnv *env, jclass clazz, std::list *fields); +std::string javaToString(JNIEnv *env, jstring str); +std::string javaObjectToString(JNIEnv *env, jobject obj); +std::string javaArrayToString(JNIEnv *env, jobjectArray arr); +std::string javaMethodCallToString(JNIEnv *env, jobject obj, jmethodID methodId, jarray args); +JNIEnv *javaGetEnv(JavaVM *jvm, jobject classLoader); +jobject getSystemClassLoader(JNIEnv *env); +jvalueType javaGetArrayComponentType(JNIEnv *env, jobjectArray array); +jvalueType javaGetType(JNIEnv *env, jclass type); +jobjectArray v8ToJava(JNIEnv *env, Nan::NAN_METHOD_ARGS_TYPE args, int start, int end); +jobject v8ToJava(JNIEnv *env, v8::Local arg); +v8::Local javaExceptionToV8(Java *java, JNIEnv *env, const std::string &alternateMessage); +v8::Local javaExceptionToV8(Java *java, JNIEnv *env, jthrowable ex, const std::string &alternateMessage); +std::string javaExceptionToString(JNIEnv *env, jthrowable ex); +void checkJavaException(JNIEnv *env); +v8::Local javaArrayToV8(Java *java, JNIEnv *env, jobjectArray objArray); +v8::Local javaToV8(Java *java, JNIEnv *env, jobject obj); +v8::Local javaToV8(Java *java, JNIEnv *env, jobject obj, DynamicProxyData *dynamicProxyData); +jobjectArray javaObjectArrayToClasses(JNIEnv *env, jobjectArray objs); +jobject longToJavaLongObj(JNIEnv *env, jlong l); +jarray javaGetArgsForMethod(JNIEnv *env, jobject method, jarray args); +jarray javaGetArgsForConstructor(JNIEnv *env, jobject method, jarray args); + +jclass javaFindClass(JNIEnv *env, const std::string &className); +jobject javaFindField(JNIEnv *env, jclass clazz, const std::string &fieldName); +jobject javaFindMethod(JNIEnv *env, jclass clazz, const std::string &methodName, jobjectArray methodArgs); +jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs); +void javaCastArguments(JNIEnv *env, jobjectArray methodArgs, jobject method); + +// TODO remove these functions after node nan gets updated +v8::Local GetHiddenValue(v8::Local object, v8::Local key); +void SetHiddenValue(v8::Local object, v8::Local key, v8::Local value); +void SetHiddenValue(v8::NumberObject *, v8::Local key, v8::Local value); + +#define assertNoException(env) \ + if (env->ExceptionCheck()) { \ + env->ExceptionDescribe(); \ + assert(false); \ + } + +std::string methodNotFoundToString(JNIEnv *env, jclass clazz, const std::string &methodNameSig, bool constructor, + Nan::NAN_METHOD_ARGS_TYPE args, int argStart, int argEnd); + +void unref(DynamicProxyData *dynamicProxyData); + +#define UNUSED_VARIABLE(var) var = var; + +#define ARGS_FRONT_OBJECT(ARGNAME) \ + if (info.Length() < argsStart + 1 || !info[argsStart]->IsObject()) { \ + std::ostringstream errStr; \ + errStr << "Argument " << (argsStart + 1) << " must be an object"; \ + Nan::ThrowError(Nan::TypeError(errStr.str().c_str())); \ + return; \ + } \ + v8::Local ARGNAME = v8::Local::Cast(info[argsStart]); \ + argsStart++; + +#define ARGS_FRONT_STRING(ARGNAME) \ + if (info.Length() < argsStart + 1 || !info[argsStart]->IsString()) { \ + std::ostringstream errStr; \ + errStr << "Argument " << (argsStart + 1) << " must be a string"; \ + Nan::ThrowError(Nan::TypeError(errStr.str().c_str())); \ + return; \ + } \ + v8::Local _##ARGNAME##_obj = v8::Local::Cast(info[argsStart]); \ + Nan::Utf8String _##ARGNAME##_val(_##ARGNAME##_obj); \ + std::string ARGNAME = *_##ARGNAME##_val; \ + argsStart++; + +#define ARGS_FRONT_CLASSNAME() ARGS_FRONT_STRING(className) + +#define ARGS_BACK_CALLBACK() \ + bool callbackProvided; \ + v8::Local callback; \ + if (info[info.Length() - 1]->IsFunction()) { \ + callback = info[argsEnd - 1]; \ + argsEnd--; \ + callbackProvided = true; \ + } else { \ + callback = Nan::Null(); \ + callbackProvided = false; \ + } + +#define EXCEPTION_CALL_CALLBACK(JAVA, STRBUILDER) \ + std::ostringstream errStr; \ + errStr << STRBUILDER; \ + v8::Local error = javaExceptionToV8(JAVA, env, errStr.str()); \ + v8::Local argv[2]; \ + argv[0] = error; \ + argv[1] = Nan::Undefined(); \ + Nan::Call(callback.As(), Nan::GetCurrentContext()->Global(), 2, argv); + +#define END_CALLBACK_FUNCTION(MSG) \ + if (callbackProvided) { \ + info.GetReturnValue().SetUndefined(); \ + return; \ + } else { \ + std::ostringstream str; \ + str << MSG; \ + info.GetReturnValue().Set(Nan::New(str.str().c_str()).ToLocalChecked()); \ + return; \ + } + +#ifndef UNUSED_VARIABLE +#define UNUSED_VARIABLE(a) a = a; +#endif + +#endif diff --git a/src-java/commons-lang3-node-java.jar b/src-java/commons-lang3-node-java.jar new file mode 100644 index 00000000..11e9172f Binary files /dev/null and b/src-java/commons-lang3-node-java.jar differ diff --git a/src-java/node/CastingUtils.class b/src-java/node/CastingUtils.class new file mode 100644 index 00000000..a449276b Binary files /dev/null and b/src-java/node/CastingUtils.class differ diff --git a/src-java/node/CastingUtils.java b/src-java/node/CastingUtils.java new file mode 100644 index 00000000..363514a2 --- /dev/null +++ b/src-java/node/CastingUtils.java @@ -0,0 +1,36 @@ +package node; + +import java.lang.reflect.Method; + +public class CastingUtils { + public static void cast(Method method, Object[] args) throws Throwable { + Class[] methodParameterTypes = method.getParameterTypes(); + if (methodParameterTypes.length != args.length) { + throw new Exception("Method argument length mismatch. Expecting " + methodParameterTypes.length + " found " + args.length); + } + for (int i = 0; i < methodParameterTypes.length; i++) { + args[i] = cast(args[i], methodParameterTypes[i]); + } + } + + public static Object cast(Object o, Class t) { + if (o == null) { + return null; + } + + Class oClass = o.getClass(); + if (oClass == Integer.class) { + Integer i = (Integer) o; + if (t == Double.class) { + return i.doubleValue(); + } + } else if (oClass == Double.class) { + Double d = (Double) o; + if (t == Integer.class) { + return d.intValue(); + } + } + + return o; + } +} diff --git a/src-java/node/MethodCallBaton.class b/src-java/node/MethodCallBaton.class new file mode 100644 index 00000000..efb946b6 Binary files /dev/null and b/src-java/node/MethodCallBaton.class differ diff --git a/src-java/node/MethodCallBaton.java b/src-java/node/MethodCallBaton.java new file mode 100644 index 00000000..ef1d0e89 --- /dev/null +++ b/src-java/node/MethodCallBaton.java @@ -0,0 +1,56 @@ +package node; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public abstract class MethodCallBaton { + public static Object newInstance(Constructor ctor, Object[] args) throws Exception { + return ctor.newInstance(args); + } + + public static Object invokeMethod(Method func, Object obj, Object[] args) throws Exception { + func.setAccessible(true); + return func.invoke(obj, args); + } + + public static Object invokeMethod9(Method func, Object obj, Object[] args) throws Exception { + if (!func.canAccess(obj)) { + Method accessible = findAccessible(func, obj, func.getDeclaringClass()); + + if (accessible != null) { + func = accessible; + } + } + + return func.invoke(obj, args); + } + + private static Method findAccessible(Method func, Object obj, Class clazz) { + Method accessible = null; + + if (clazz != null) { + try { + accessible = clazz.getMethod(func.getName(), func.getParameterTypes()); + accessible = accessible.canAccess(obj) ? accessible : null; + } catch (Exception ignored) { + accessible = null; + } + + if (accessible == null) { + accessible = findAccessible(func, obj, clazz.getSuperclass()); + } + + if (accessible == null) { + for (Class iface : clazz.getInterfaces()) { + accessible = findAccessible(func, obj, iface); + + if (accessible != null) { + break; + } + } + } + } + + return accessible; + } +} diff --git a/src-java/node/NodeDynamicProxyClass.class b/src-java/node/NodeDynamicProxyClass.class index d879c963..95373a4b 100644 Binary files a/src-java/node/NodeDynamicProxyClass.class and b/src-java/node/NodeDynamicProxyClass.class differ diff --git a/src-java/node/NodeDynamicProxyClass.java b/src-java/node/NodeDynamicProxyClass.java index 2ecb8307..27943eef 100644 --- a/src-java/node/NodeDynamicProxyClass.java +++ b/src-java/node/NodeDynamicProxyClass.java @@ -1,9 +1,22 @@ package node; -public class NodeDynamicProxyClass implements java.lang.reflect.InvocationHandler -{ +import java.lang.reflect.Method; + +public class NodeDynamicProxyClass implements java.lang.reflect.InvocationHandler { + private static final Method EQUALS; + private static final Method HASHCODE; + static { + try { + EQUALS = Object.class.getMethod("equals", Object.class); + HASHCODE = Object.class.getMethod("hashCode"); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); + } + } + private native Object callJs(long ptr, java.lang.reflect.Method m, Object[] args) throws Throwable; - public long ptr; + private native void unref(long ptr) throws Throwable; + public final long ptr; public NodeDynamicProxyClass(String path, long ptr) { try{ @@ -14,16 +27,33 @@ public NodeDynamicProxyClass(String path, long ptr) { this.ptr = ptr; } + @Override public Object invoke(Object proxy, java.lang.reflect.Method m, Object[] args) throws Throwable { - return callJs(this.ptr, m, args); - } - - public void ref() { - + try { + Object result = callJs(this.ptr, m, args); + //if(result == null) { + // System.out.println("invoke: null"); + //} else { + // System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString()); + //} + return result; + } catch (NoSuchMethodError e) { + // use 'vanilla' implementations otherwise - the object that persists between multiple invocations is + // 'this', not the 'proxy' argument, so we operate on this. + if (EQUALS.equals(m)) { + // need to check if the arg is a Proxy, and if so, if its invocation handler == this! + return args[0] == proxy; + } else if (HASHCODE.equals(m)) { + return System.identityHashCode(proxy); + } else if ("unref".equals(m.getName()) && m.getParameterTypes().length == 0 && m.getReturnType() == Void.TYPE) { + this.unref(); + } + throw e; + } } - public void unref() { - + public void unref() throws Throwable { + unref(this.ptr); } } diff --git a/src-java/node/NodeJsException.class b/src-java/node/NodeJsException.class new file mode 100644 index 00000000..1697d05e Binary files /dev/null and b/src-java/node/NodeJsException.class differ diff --git a/src-java/node/NodeJsException.java b/src-java/node/NodeJsException.java new file mode 100644 index 00000000..35c5e961 --- /dev/null +++ b/src-java/node/NodeJsException.java @@ -0,0 +1,7 @@ +package node; + +public class NodeJsException extends RuntimeException { + public NodeJsException(String message) { + super(message); + } +} diff --git a/src-java/node/VarArgs.class b/src-java/node/VarArgs.class new file mode 100644 index 00000000..6f990ec7 Binary files /dev/null and b/src-java/node/VarArgs.class differ diff --git a/src-java/node/VarArgs.java b/src-java/node/VarArgs.java new file mode 100644 index 00000000..001966bd --- /dev/null +++ b/src-java/node/VarArgs.java @@ -0,0 +1,43 @@ +package node; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Array; +import java.util.Arrays; + +public class VarArgs { + public static Object[] getVarArgs(Method method, Object[] args) { + if (method.isVarArgs()) { + Class[] methodParameterTypes = method.getParameterTypes(); + return getVarArgs(args, methodParameterTypes); + } + return args; + } + + public static Object[] getVarArgs(Constructor constructor, Object[] args) { + if (constructor.isVarArgs()) { + Class[] constructorParameterTypes = constructor.getParameterTypes(); + return getVarArgs(args, constructorParameterTypes); + } + return args; + } + + public static Object[] getVarArgs(Object[] args, Class[] methodParameterTypes) { + if(args.length == methodParameterTypes.length + && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) { + return args; + } + + Object[] newArgs = new Object[methodParameterTypes.length]; + System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1); + Class varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); + int varArgLength = args.length - methodParameterTypes.length + 1; + Object[] varArgsArray = (Object[])Array.newInstance(varArgComponentType, varArgLength); +// System.out.println("varArgComponentType: " + varArgComponentType); +// System.out.println("varArgsArray: " + Arrays.asList(varArgsArray).toString()); +// System.out.println("args: " + Arrays.asList(args).toString()); + System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength); + newArgs[methodParameterTypes.length - 1] = varArgsArray; + return newArgs; + } +} diff --git a/src-node/nodeJavaBridge.js b/src-node/nodeJavaBridge.js new file mode 100644 index 00000000..01f39978 --- /dev/null +++ b/src-node/nodeJavaBridge.js @@ -0,0 +1,327 @@ +"use strict"; + +process.env.PATH += require("../build/jvm_dll_path.json"); + +const path = require("path"); +const fs = require("fs"); +const util = require("util"); + +let binaryPath = null; +try { + if (fs.statSync && fs.statSync(path.join(__dirname, "../build/Debug/nodejavabridge_bindings.node")).isFile()) { + binaryPath = path.resolve(path.join(__dirname, "../build/Debug/nodejavabridge_bindings.node")); + console.log("****** NODE-JAVA RUNNING IN DEBUG MODE ******"); + } +} catch (_err) { + // do nothing fs.statSync just couldn't find the file +} +if (!binaryPath) { + binaryPath = path.resolve(path.join(__dirname, "../build/Release/nodejavabridge_bindings.node")); +} +const bindings = require(binaryPath); + +const java = (module.exports = new bindings.Java()); +java.promisify = util.promisify; +java.classpath.push(path.resolve(__dirname, "../src-java/commons-lang3-node-java.jar")); +java.classpath.push(path.resolve(__dirname, __dirname, "../src-java")); +java.classpath.pushDir = function (dir) { + fs.readdirSync(dir).forEach(function (file) { + java.classpath.push(path.resolve(dir, file)); + }); +}; +java.nativeBindingLocation = binaryPath; + +const callStaticMethod = java.callStaticMethod; +const callStaticMethodSync = java.callStaticMethodSync; +const newInstanceSync = java.newInstanceSync; + +let syncSuffix = undefined; +let asyncSuffix = undefined; +let ifReadOnlySuffix = "_"; + +const SyncCall = function (obj, method) { + if (syncSuffix === undefined) { + throw new Error("Sync call made before jvm created"); + } + const syncMethodName = method + syncSuffix; + if (syncMethodName in obj) { + return obj[syncMethodName].bind(obj); + } else { + throw new Error("Sync method not found:" + syncMethodName); + } +}; + +java.isJvmCreated = function () { + return typeof java.onJvmCreated !== "function"; +}; + +const clients = []; + +// We provide two methods for 'clients' of node-java to 'register' their use of java. +// By registering, a client gets the opportunity to be called asynchronously just before the JVM is created, +// and just after the JVM is created. The before hook function will typically be used to add to java.classpath. +// The function may peform asynchronous operations, such as async [glob](https://github.com/isaacs/node-glob) +// resolutions of wild-carded file system paths, and then notify when it has finished via either calling +// a node-style callback function, or by resolving a promise. + +// A client can register function hooks to be called before and after the JVM is created. +// If the client doesn't need to be called back for either function, it can pass null or undefined. +// Both before and after here are assumed to be functions that accept one argument that is a node-callback function. +java.registerClient = function (before, after) { + if (java.isJvmCreated()) { + throw new Error("java.registerClient() called after JVM already created."); + } + const before_ = + before && before.length === 0 + ? function (cb) { + before(); + cb(); + } + : before; + + const after_ = + after && after.length === 0 + ? function (cb) { + after(); + cb(); + } + : after; + + clients.push({ before: before_, after: after_ }); +}; + +// A client can register function hooks to be called before and after the JVM is created. +// If the client doesn't need to be called back for either function, it can pass null or undefined. +// Both before and after here are assumed to be functions that return Promises/A+ `thenable` objects. +java.registerClientP = function (beforeP, afterP) { + if (java.isJvmCreated()) { + throw new Error("java.registerClient() called after JVM already created."); + } + clients.push({ beforeP: beforeP, afterP: afterP }); +}; + +async function runBeforeHooks() { + for (const client of clients) { + if (client.before) { + await new Promise((resolve, reject) => { + client.before((err) => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); + } + if (client.beforeP) { + await client.beforeP(); + } + } +} + +function createJVMAsync() { + const _ignore = java.newLong(0); // called just for the side effect that it will create the JVM +} + +async function runAfterHooks() { + for (const client of clients) { + if (client.after) { + await new Promise((resolve, reject) => { + client.after((err) => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); + } + if (client.afterP) { + await client.afterP(); + } + } +} + +async function initializeAll() { + await runBeforeHooks(); + createJVMAsync(); + await runAfterHooks(); +} + +// This function ensures that the JVM has been launched, asynchronously. The application can be notified +// when the JVM is fully created via either a node callback function, or via a promise. +// If the parameter `callback` is provided, it is assume be a node callback function. +// This function may be called multiple times -- the 2nd and subsequent calls are no-ops. +// However, once this method has been called (or the JVM is launched as a side effect of calling other java +// methods), then clients can no longer use the registerClient API. +java.ensureJvm = function (callback) { + // First see if the promise-style API should be used. + // This must be done first in order to ensure the proper API is used. + if (typeof callback === "undefined") { + // Create a promisified version of this function. + const launchJvmPromise = util.promisify(java.ensureJvm.bind(java)); + // Call the promisified function, returning its result, which should be a promise. + return launchJvmPromise(); + } + + // If we get here, callback must be a node-style callback function. If not, throw an error. + else if (typeof callback !== "function") { + throw new Error("java.launchJvm(cb) requires its one argument to be a callback function."); + } + + // Now check if the JVM has already been created. If so, we assume that the jvm was already successfully + // launched, and we can just implement idempotent behavior, i.e. silently notify that the JVM has been created. + else if (java.isJvmCreated()) { + return setImmediate(callback); + } + + // Finally, queue the initializeAll function. + else { + return setImmediate(async () => { + try { + await initializeAll(); + callback(); + } catch (err) { + callback(err); + } + }); + } +}; + +java.onJvmCreated = function () { + if (java.asyncOptions) { + syncSuffix = java.asyncOptions.syncSuffix; + asyncSuffix = java.asyncOptions.asyncSuffix; + if (typeof syncSuffix !== "string") { + throw new Error("In asyncOptions, syncSuffix must be defined and must a string"); + } + const promiseSuffix = java.asyncOptions.promiseSuffix; + if (typeof promiseSuffix === "string") { + const methods = ["newInstance", "callMethod", "callStaticMethod"]; + methods.forEach(function (name) { + java[name + promiseSuffix] = util.promisify(java[name]); + }); + } + + if (typeof java.asyncOptions.ifReadOnlySuffix === "string" && java.asyncOptions.ifReadOnlySuffix !== "") { + ifReadOnlySuffix = java.asyncOptions.ifReadOnlySuffix; + } + } else { + syncSuffix = "Sync"; + asyncSuffix = ""; + } +}; + +const MODIFIER_PUBLIC = 1; +const MODIFIER_STATIC = 8; + +function isWritable(prop) { + // If the property has no descriptor, or wasn't explicitly marked as not writable or not configurable, assume it is. + // We check both desc.writable and desc.configurable, since checking desc.writable alone is not sufficient + // (e.g. for either .caller or .arguments). + // It may be that checking desc.configurable is sufficient, but the specification doesn't make this definitive, + // and there is no harm in checking both. + if (prop === "caller" || prop === "arguments") { + return false; + } + + const desc = Object.getOwnPropertyDescriptor(function () {}, prop) || {}; + return desc.writable !== false && desc.configurable !== false; +} + +function usableName(name) { + if (!isWritable(name)) { + name = name + ifReadOnlySuffix; + } + return name; +} + +java.import = function (name) { + const clazz = java.findClassSync(name); // TODO: change to Class.forName when classloader issue is resolved. + const result = function javaClassConstructorProxy() { + const args = [name]; + for (let i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + return newInstanceSync.apply(java, args); + }; + + result.class = clazz; + + // copy static fields + const fields = SyncCall(clazz, "getDeclaredFields")(); + for (let i = 0; i < fields.length; i++) { + const modifiers = SyncCall(fields[i], "getModifiers")(); + if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { + const fieldName = SyncCall(fields[i], "getName")(); + const jsfieldName = usableName(fieldName); + result.__defineGetter__( + jsfieldName, + function (name, fieldName) { + return java.getStaticFieldValue(name, fieldName); + }.bind(this, name, fieldName) + ); + result.__defineSetter__( + jsfieldName, + function (name, fieldName, val) { + java.setStaticFieldValue(name, fieldName, val); + }.bind(this, name, fieldName) + ); + } + } + + let promiseSuffix; + if (java.asyncOptions) { + promiseSuffix = java.asyncOptions.promiseSuffix; + } + + // copy static methods + const methods = SyncCall(clazz, "getDeclaredMethods")(); + for (let i = 0; i < methods.length; i++) { + const modifiers = SyncCall(methods[i], "getModifiers")(); + if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { + const methodName = SyncCall(methods[i], "getName")(); + + if (typeof syncSuffix === "string") { + const syncName = usableName(methodName + syncSuffix); + result[syncName] = callStaticMethodSync.bind(java, name, methodName); + } + + if (typeof asyncSuffix === "string") { + const asyncName = usableName(methodName + asyncSuffix); + result[asyncName] = callStaticMethod.bind(java, name, methodName); + } + + if (typeof promiseSuffix === "string") { + const promiseName = usableName(methodName + promiseSuffix); + result[promiseName] = util.promisify(callStaticMethod.bind(java, name, methodName)); + } + } + } + + // copy static classes/enums + const classes = SyncCall(clazz, "getDeclaredClasses")(); + for (let i = 0; i < classes.length; i++) { + const modifiers = SyncCall(classes[i], "getModifiers")(); + if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { + const className = SyncCall(classes[i], "getName")(); + const simpleName = SyncCall(classes[i], "getSimpleName")(); + Object.defineProperty(result, simpleName, { + get: function (result, simpleName, className) { + const c = java.import(className); + + // memoize the import + const d = Object.getOwnPropertyDescriptor(result, simpleName); + d.get = function (c) { + return c; + }.bind(null, c); + Object.defineProperty(result, simpleName, d); + + return c; + }.bind(this, result, simpleName, className), + enumerable: true, + configurable: true, + }); + } + } + + return result; +}; diff --git a/src/java.cpp b/src/java.cpp deleted file mode 100644 index 404d3714..00000000 --- a/src/java.cpp +++ /dev/null @@ -1,697 +0,0 @@ - -#include "java.h" -#include -#include -#include "javaObject.h" -#include "methodCallBaton.h" -#include "node_NodeDynamicProxyClass.h" -#include - -std::string nativeBindingLocation; -long v8ThreadId; - -/*static*/ v8::Persistent Java::s_ct; - -void my_sleep(int dur) { -#ifdef WIN32 - Sleep(dur); -#else - usleep(dur); -#endif -} - -long my_getThreadId() { -#ifdef WIN32 - return (long)GetCurrentThreadId(); -#else - return (long)pthread_self(); -#endif -} - -/*static*/ void Java::Init(v8::Handle target) { - v8::HandleScope scope; - - v8ThreadId = my_getThreadId(); - - v8::Local t = v8::FunctionTemplate::New(New); - s_ct = v8::Persistent::New(t); - s_ct->InstanceTemplate()->SetInternalFieldCount(1); - s_ct->SetClassName(v8::String::NewSymbol("Java")); - - NODE_SET_PROTOTYPE_METHOD(s_ct, "newInstance", newInstance); - NODE_SET_PROTOTYPE_METHOD(s_ct, "newInstanceSync", newInstanceSync); - NODE_SET_PROTOTYPE_METHOD(s_ct, "newProxy", newProxy); - NODE_SET_PROTOTYPE_METHOD(s_ct, "callStaticMethod", callStaticMethod); - NODE_SET_PROTOTYPE_METHOD(s_ct, "callStaticMethodSync", callStaticMethodSync); - NODE_SET_PROTOTYPE_METHOD(s_ct, "findClassSync", findClassSync); - NODE_SET_PROTOTYPE_METHOD(s_ct, "newArray", newArray); - NODE_SET_PROTOTYPE_METHOD(s_ct, "newByte", newByte); - NODE_SET_PROTOTYPE_METHOD(s_ct, "getStaticFieldValue", getStaticFieldValue); - NODE_SET_PROTOTYPE_METHOD(s_ct, "setStaticFieldValue", setStaticFieldValue); - - target->Set(v8::String::NewSymbol("Java"), s_ct->GetFunction()); -} - -/*static*/ v8::Handle Java::New(const v8::Arguments& args) { - v8::HandleScope scope; - - Java *self = new Java(); - self->Wrap(args.This()); - - self->handle_->Set(v8::String::New("classpath"), v8::Array::New()); - self->handle_->Set(v8::String::New("options"), v8::Array::New()); - self->handle_->Set(v8::String::New("nativeBindingLocation"), v8::String::New("Not Set")); - - return args.This(); -} - -Java::Java() { - this->m_jvm = NULL; - this->m_env = NULL; -} - -Java::~Java() { - -} - -v8::Handle Java::ensureJvm() { - if(!m_jvm) { - return createJVM(&this->m_jvm, &this->m_env); - } - - return v8::Undefined(); -} - -v8::Handle Java::createJVM(JavaVM** jvm, JNIEnv** env) { - JavaVM* jvmTemp; - JavaVMInitArgs args; - - // setup classpath - std::ostringstream classPath; - classPath << "-Djava.class.path="; - - v8::Local classPathValue = handle_->Get(v8::String::New("classpath")); - if(!classPathValue->IsArray()) { - return ThrowException(v8::Exception::TypeError(v8::String::New("Classpath must be an array"))); - } - v8::Local classPathArray = v8::Array::Cast(*classPathValue); - for(uint32_t i=0; iLength(); i++) { - if(i != 0) { - #ifdef WIN32 - classPath << ";"; - #else - classPath << ":"; - #endif - } - v8::Local arrayItemValue = classPathArray->Get(i); - if(!arrayItemValue->IsString()) { - return ThrowException(v8::Exception::TypeError(v8::String::New("Classpath must only contain strings"))); - } - v8::Local arrayItem = arrayItemValue->ToString(); - v8::String::AsciiValue arrayItemStr(arrayItem); - classPath << *arrayItemStr; - } - - // set the native binding location - v8::Local v8NativeBindingLocation = handle_->Get(v8::String::New("nativeBindingLocation")); - v8::String::AsciiValue nativeBindingLocationStr(v8NativeBindingLocation); - nativeBindingLocation = *nativeBindingLocationStr; - - // get other options - v8::Local optionsValue = handle_->Get(v8::String::New("options")); - if(!optionsValue->IsArray()) { - return ThrowException(v8::Exception::TypeError(v8::String::New("options must be an array"))); - } - v8::Local optionsArray = v8::Array::Cast(*optionsValue); - - // create vm options - int vmOptionsCount = optionsArray->Length() + 1; - JavaVMOption* vmOptions = new JavaVMOption[vmOptionsCount]; - vmOptions[0].optionString = strdup(classPath.str().c_str()); - for(uint32_t i=0; iLength(); i++) { - v8::Local arrayItemValue = optionsArray->Get(i); - if(!arrayItemValue->IsString()) { - return ThrowException(v8::Exception::TypeError(v8::String::New("options must only contain strings"))); - } - v8::Local arrayItem = arrayItemValue->ToString(); - v8::String::AsciiValue arrayItemStr(arrayItem); - vmOptions[i+1].optionString = strdup(*arrayItemStr); - } - - JNI_GetDefaultJavaVMInitArgs(&args); - args.version = JNI_VERSION_1_6; - args.ignoreUnrecognized = false; - args.options = vmOptions; - args.nOptions = vmOptionsCount; - JNI_CreateJavaVM(&jvmTemp, (void **)env, &args); - *jvm = jvmTemp; - - return v8::Undefined(); -} - -/*static*/ v8::Handle Java::newInstance(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - ARGS_BACK_CALLBACK(); - - // find class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - EXCEPTION_CALL_CALLBACK("Could not find class " << className.c_str()); - return v8::Undefined(); - } - - // get method - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - jobject method = javaFindConstructor(env, clazz, methodArgs); - if(method == NULL) { - EXCEPTION_CALL_CALLBACK("Could not find constructor for class " << className); - return v8::Undefined(); - } - - // run - NewInstanceBaton* baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); - baton->run(); - - END_CALLBACK_FUNCTION("\"Constructor for class '" << className << "' called without a callback did you mean to use the Sync version?\""); -} - -/*static*/ v8::Handle Java::newInstanceSync(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - - // find class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // find method - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - jobject method = javaFindConstructor(env, clazz, methodArgs); - if(method == NULL) { - std::ostringstream errStr; - errStr << "Could not find constructor for class " << className.c_str(); - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // run - v8::Handle callback = v8::Object::New(); - NewInstanceBaton* baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); - v8::Handle result = baton->runSync(); - delete baton; - if(result->IsNativeError()) { - return ThrowException(result); - } - return scope.Close(result); -} - -/*static*/ v8::Handle Java::newProxy(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - ARGS_FRONT_STRING(interfaceName); - ARGS_FRONT_OBJECT(functions); - - DynamicProxyData* dynamicProxyData = new DynamicProxyData(); - dynamicProxyData->markerStart = DYNAMIC_PROXY_DATA_MARKER_START; - dynamicProxyData->markerEnd = DYNAMIC_PROXY_DATA_MARKER_END; - dynamicProxyData->java = self; - dynamicProxyData->interfaceName = interfaceName; - dynamicProxyData->functions = v8::Persistent::New(functions); - - // find NodeDynamicProxyClass - std::string className = "node.NodeDynamicProxyClass"; - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class node/NodeDynamicProxyClass"; - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // find constructor - jclass objectClazz = env->FindClass("java/lang/Object"); - jobjectArray methodArgs = env->NewObjectArray(2, objectClazz, NULL); - env->SetObjectArrayElement(methodArgs, 0, v8ToJava(env, v8::String::New(nativeBindingLocation.c_str()))); - env->SetObjectArrayElement(methodArgs, 1, longToJavaLongObj(env, (long)dynamicProxyData)); - jobject method = javaFindConstructor(env, clazz, methodArgs); - if(method == NULL) { - std::ostringstream errStr; - errStr << "Could not find constructor for class node/NodeDynamicProxyClass"; - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // run constructor - v8::Handle callback = v8::Object::New(); - NewInstanceBaton* baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); - v8::Handle result = baton->runSync(); - delete baton; - if(result->IsNativeError()) { - return ThrowException(result); - } - return scope.Close(result); -} - -/*static*/ v8::Handle Java::callStaticMethod(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - ARGS_FRONT_STRING(methodName); - ARGS_BACK_CALLBACK(); - - // find class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - EXCEPTION_CALL_CALLBACK("Could not create class " << className.c_str()); - return v8::Undefined(); - } - - // find method - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - jobject method = javaFindMethod(env, clazz, methodName, methodArgs); - if(method == NULL) { - EXCEPTION_CALL_CALLBACK("Could not find method \"" << methodName.c_str() << "\""); - return v8::Undefined(); - } - - // run - StaticMethodCallBaton* baton = new StaticMethodCallBaton(self, clazz, method, methodArgs, callback); - baton->run(); - - END_CALLBACK_FUNCTION("\"Static method '" << methodName << "' called without a callback did you mean to use the Sync version?\""); -} - -/*static*/ v8::Handle Java::callStaticMethodSync(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - ARGS_FRONT_STRING(methodName); - - // find class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // find method - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - jobject method = javaFindMethod(env, clazz, methodName, methodArgs); - if(method == NULL) { - std::ostringstream errStr; - errStr << "Could not find method \"" << methodName.c_str() << "\""; - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // run - v8::Handle callback = v8::Object::New(); - StaticMethodCallBaton* baton = new StaticMethodCallBaton(self, clazz, method, methodArgs, callback); - v8::Handle result = baton->runSync(); - delete baton; - if(result->IsNativeError()) { - return ThrowException(result); - } - return scope.Close(result); -} - -/*static*/ v8::Handle Java::findClassSync(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - - // find class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // run - v8::Handle result = javaToV8(self, env, clazz); - return scope.Close(result); -} - -/*static*/ v8::Handle Java::newArray(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - - // argument - array - if(args.Length() < argsStart+1 || !args[argsStart]->IsArray()) { - std::ostringstream errStr; - errStr << "Argument " << (argsStart+1) << " must be an array"; - return ThrowException(v8::Exception::TypeError(v8::String::New(errStr.str().c_str()))); - } - v8::Local arrayObj = v8::Local::Cast(args[argsStart]); - - UNUSED_VARIABLE(argsEnd); - - // find class and method - jarray results; - if(strcmp(className.c_str(), "byte") == 0) { - results = env->NewByteArray(arrayObj->Length()); - for(uint32_t i=0; iLength(); i++) { - v8::Local item = arrayObj->Get(i); - jobject val = v8ToJava(env, item); - jclass byteClazz = env->FindClass("java/lang/Byte"); - jmethodID byte_byteValue = env->GetMethodID(byteClazz, "byteValue", "()B"); - jbyte byteValues[1]; - byteValues[0] = env->CallByteMethod(val, byte_byteValue); - env->SetByteArrayRegion((jbyteArray)results, i, 1, byteValues); - } - } - - else - { - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - - // create array - results = env->NewObjectArray(arrayObj->Length(), clazz, NULL); - - for(uint32_t i=0; iLength(); i++) { - v8::Local item = arrayObj->Get(i); - jobject val = v8ToJava(env, item); - env->SetObjectArrayElement((jobjectArray)results, i, val); - if(env->ExceptionOccurred()) { - std::ostringstream errStr; - v8::String::AsciiValue valStr(item); - errStr << "Could not add item \"" << *valStr << "\" to array."; - return ThrowException(javaExceptionToV8(env, errStr.str())); - } - } - } - - return scope.Close(JavaObject::New(self, results)); -} - -/*static*/ v8::Handle Java::newByte(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - - if(args.Length() != 1) { - return ThrowException(v8::Exception::TypeError(v8::String::New("newByte only takes 1 argument"))); - } - - // argument - value - if(!args[0]->IsNumber()) { - return ThrowException(v8::Exception::TypeError(v8::String::New("Argument 1 must be a number"))); - } - - v8::Local val = args[0]->ToNumber(); - - jclass clazz = env->FindClass("java/lang/Byte"); - jmethodID constructor = env->GetMethodID(clazz, "", "(B)V"); - jobject newObj = env->NewObject(clazz, constructor, (jbyte)val->Value()); - - return scope.Close(JavaObject::New(self, newObj)); -} - -/*static*/ v8::Handle Java::getStaticFieldValue(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - PUSH_LOCAL_JAVA_FRAME(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - ARGS_FRONT_STRING(fieldName); - UNUSED_VARIABLE(argsEnd); - - // find the class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - // get the field - jobject field = javaFindField(env, clazz, fieldName); - if(field == NULL) { - std::ostringstream errStr; - errStr << "Could not find field " << fieldName.c_str() << " on class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_get = env->GetMethodID(fieldClazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); - - // get field value - jobject val = env->CallObjectMethod(field, field_get, NULL); - if(env->ExceptionOccurred()) { - std::ostringstream errStr; - errStr << "Could not get field " << fieldName.c_str() << " on class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - env->DeleteLocalRef(clazz); - env->DeleteLocalRef(field); - env->DeleteLocalRef(fieldClazz); - - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(javaToV8(self, env, val))); -} - -/*static*/ v8::Handle Java::setStaticFieldValue(const v8::Arguments& args) { - v8::HandleScope scope; - Java* self = node::ObjectWrap::Unwrap(args.This()); - v8::Handle ensureJvmResults = self->ensureJvm(); - if(!ensureJvmResults->IsUndefined()) { - return ensureJvmResults; - } - JNIEnv* env = self->getJavaEnv(); - PUSH_LOCAL_JAVA_FRAME(); - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_FRONT_CLASSNAME(); - ARGS_FRONT_STRING(fieldName); - - // argument - new value - if(args.Length() < argsStart+1) { - std::ostringstream errStr; - errStr << "setStaticFieldValue requires " << (argsStart+1) << " arguments"; - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(v8::Exception::TypeError(v8::String::New(errStr.str().c_str())))); - } - jobject newValue = v8ToJava(env, args[argsStart]); - argsStart++; - - UNUSED_VARIABLE(argsEnd); - - // find the class - jclass clazz = javaFindClass(env, className); - if(clazz == NULL) { - std::ostringstream errStr; - errStr << "Could not create class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - // get the field - jobject field = javaFindField(env, clazz, fieldName); - if(field == NULL) { - std::ostringstream errStr; - errStr << "Could not find field " << fieldName.c_str() << " on class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_set = env->GetMethodID(fieldClazz, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - - //printf("newValue: %s\n", javaObjectToString(env, newValue).c_str()); - - // set field value - env->CallObjectMethod(field, field_set, NULL, newValue); - if(env->ExceptionOccurred()) { - std::ostringstream errStr; - errStr << "Could not set field " << fieldName.c_str() << " on class " << className.c_str(); - POP_LOCAL_JAVA_FRAME_AND_RETURN(ThrowException(javaExceptionToV8(env, errStr.str()))); - } - - POP_LOCAL_JAVA_FRAME_AND_RETURN(v8::Undefined()); -} - -void EIO_CallJs(uv_work_t* req) { -} - -void EIO_AfterCallJs(uv_work_t* req) { - DynamicProxyData* dynamicProxyData = static_cast(req->data); - if(!dynamicProxyDataVerify(dynamicProxyData)) { - return; - } - dynamicProxyData->result = NULL; - - JNIEnv* env = dynamicProxyData->env; - - v8::HandleScope scope; - v8::Array* v8Args; - v8::Function* fn; - v8::Handle* argv; - int argc; - int i; - v8::Local v8Result; - jobject javaResult; - - v8::Local fnObj = dynamicProxyData->functions->Get(v8::String::New(dynamicProxyData->methodName.c_str())); - if(fnObj->IsUndefined() || fnObj->IsNull()) { - printf("ERROR: Could not find method %s\n", dynamicProxyData->methodName.c_str()); - goto CleanUp; - } - if(!fnObj->IsFunction()) { - printf("ERROR: %s is not a function.\n", dynamicProxyData->methodName.c_str()); - goto CleanUp; - } - - fn = v8::Function::Cast(*fnObj); - - if(dynamicProxyData->args) { - v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, dynamicProxyData->args)); - argc = v8Args->Length(); - } else { - argc = 0; - } - argv = new v8::Handle[argc]; - for(i=0; iGet(i); - } - v8Result = fn->Call(dynamicProxyData->functions, argc, argv); - delete[] argv; - if(!dynamicProxyDataVerify(dynamicProxyData)) { - return; - } - - javaResult = v8ToJava(env, v8Result); - if(javaResult == NULL) { - dynamicProxyData->result = NULL; - } else { - dynamicProxyData->result = env->NewGlobalRef(javaResult); - } - -CleanUp: - dynamicProxyData->done = true; -} - -JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, jobjectArray args) { - long myThreadId = my_getThreadId(); - - DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr; - dynamicProxyData->env = env; - dynamicProxyData->args = args; - dynamicProxyData->done = false; - dynamicProxyData->result = NULL; - - jclass methodClazz = env->FindClass("java/lang/reflect/Method"); - jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;"); - dynamicProxyData->methodName = javaObjectToString(env, env->CallObjectMethod(method, method_getName)); - - uv_work_t* req = new uv_work_t(); - req->data = dynamicProxyData; - if(myThreadId == v8ThreadId) { - EIO_AfterCallJs(req); - } else { - uv_queue_work(uv_default_loop(), req, EIO_CallJs, EIO_AfterCallJs); - - while(!dynamicProxyData->done) { - my_sleep(100); - } - } - - if(!dynamicProxyDataVerify(dynamicProxyData)) { - return NULL; - } - if(dynamicProxyData->result) { - env->DeleteGlobalRef(dynamicProxyData->result); - } - return dynamicProxyData->result; -} diff --git a/src/java.h b/src/java.h deleted file mode 100644 index 9eb58e71..00000000 --- a/src/java.h +++ /dev/null @@ -1,40 +0,0 @@ - -#ifndef _node_java_h_ -#define _node_java_h_ - -#include -#include -#include -#include - -class Java : public node::ObjectWrap { -public: - static void Init(v8::Handle target); - JavaVM* getJvm() { return m_jvm; } - JNIEnv* getJavaEnv() { return m_env; } - -private: - Java(); - ~Java(); - v8::Handle createJVM(JavaVM** jvm, JNIEnv** env); - - static v8::Handle New(const v8::Arguments& args); - static v8::Handle newInstance(const v8::Arguments& args); - static v8::Handle newInstanceSync(const v8::Arguments& args); - static v8::Handle newProxy(const v8::Arguments& args); - static v8::Handle callStaticMethod(const v8::Arguments& args); - static v8::Handle callStaticMethodSync(const v8::Arguments& args); - static v8::Handle findClassSync(const v8::Arguments& args); - static v8::Handle newArray(const v8::Arguments& args); - static v8::Handle newByte(const v8::Arguments& args); - static v8::Handle getStaticFieldValue(const v8::Arguments& args); - static v8::Handle setStaticFieldValue(const v8::Arguments& args); - v8::Handle ensureJvm(); - - static v8::Persistent s_ct; - JavaVM* m_jvm; - JNIEnv* m_env; - std::string m_classPath; -}; - -#endif diff --git a/src/javaObject.cpp b/src/javaObject.cpp deleted file mode 100644 index 39a61003..00000000 --- a/src/javaObject.cpp +++ /dev/null @@ -1,251 +0,0 @@ - -#include "javaObject.h" -#include "java.h" -#include "utils.h" -#include - -/*static*/ v8::Persistent JavaObject::s_ct; - -/*static*/ void JavaObject::Init(v8::Handle target) { - v8::HandleScope scope; - - v8::Local t = v8::FunctionTemplate::New(); - s_ct = v8::Persistent::New(t); - s_ct->InstanceTemplate()->SetInternalFieldCount(1); - s_ct->SetClassName(v8::String::NewSymbol("JavaObject")); - - target->Set(v8::String::NewSymbol("JavaObject"), s_ct->GetFunction()); -} - -/*static*/ v8::Local JavaObject::New(Java *java, jobject obj) { - v8::HandleScope scope; - - JNIEnv *env = java->getJavaEnv(); - PUSH_LOCAL_JAVA_FRAME(); - - v8::Local ctor = s_ct->GetFunction(); - v8::Local javaObjectObj = ctor->NewInstance(); - JavaObject *self = new JavaObject(java, obj); - self->Wrap(javaObjectObj); - - std::list methods; - javaReflectionGetMethods(env, self->m_class, &methods); - jclass methodClazz = env->FindClass("java/lang/reflect/Method"); - jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;"); - for(std::list::iterator it = methods.begin(); it != methods.end(); it++) { - jstring methodNameJava = (jstring)env->CallObjectMethod(*it, method_getName); - std::string methodNameStr = javaToString(env, methodNameJava); - - v8::Handle methodName = v8::String::New(methodNameStr.c_str()); - v8::Local methodCallTemplate = v8::FunctionTemplate::New(methodCall, methodName); - javaObjectObj->Set(methodName, methodCallTemplate->GetFunction()); - - v8::Handle methodNameSync = v8::String::New((methodNameStr + "Sync").c_str()); - v8::Local methodCallSyncTemplate = v8::FunctionTemplate::New(methodCallSync, methodName); - javaObjectObj->Set(methodNameSync, methodCallSyncTemplate->GetFunction()); - - env->DeleteLocalRef(methodNameJava); - env->DeleteLocalRef(*it); - } - - std::list fields; - javaReflectionGetFields(env, self->m_class, &fields); - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_getName = env->GetMethodID(fieldClazz, "getName", "()Ljava/lang/String;"); - for(std::list::iterator it = fields.begin(); it != fields.end(); it++) { - jstring fieldNameJava = (jstring)env->CallObjectMethod(*it, field_getName); - std::string fieldNameStr = javaToString(env, fieldNameJava); - - v8::Handle fieldName = v8::String::New(fieldNameStr.c_str()); - javaObjectObj->SetAccessor(fieldName, fieldGetter, fieldSetter); - - env->DeleteLocalRef(fieldNameJava); - env->DeleteLocalRef(*it); - } - - env->DeleteLocalRef(obj); - - POP_LOCAL_JAVA_FRAME(); - - return scope.Close(javaObjectObj); -} - -JavaObject::JavaObject(Java *java, jobject obj) { - m_java = java; - JNIEnv *env = m_java->getJavaEnv(); - m_obj = env->NewGlobalRef(obj); - m_class = (jclass)env->NewGlobalRef(env->GetObjectClass(obj)); -} - -JavaObject::~JavaObject() { - JNIEnv *env = m_java->getJavaEnv(); - - jclass nodeDynamicProxyClass = env->FindClass("node/NodeDynamicProxyClass"); - if(env->IsInstanceOf(m_obj, nodeDynamicProxyClass)) { - jfieldID ptrField = env->GetFieldID(nodeDynamicProxyClass, "ptr", "J"); - DynamicProxyData* proxyData = (DynamicProxyData*)(long)env->GetLongField(m_obj, ptrField); - if(dynamicProxyDataVerify(proxyData)) { - delete proxyData; - } - } - - env->DeleteGlobalRef(m_obj); - env->DeleteGlobalRef(m_class); -} - -/*static*/ v8::Handle JavaObject::methodCall(const v8::Arguments& args) { - v8::HandleScope scope; - JavaObject* self = node::ObjectWrap::Unwrap(args.This()); - JNIEnv *env = self->m_java->getJavaEnv(); - - PUSH_LOCAL_JAVA_FRAME(); - - v8::String::AsciiValue methodName(args.Data()); - std::string methodNameStr = *methodName; - - int argsStart = 0; - int argsEnd = args.Length(); - - // arguments - ARGS_BACK_CALLBACK(); - - if(!callbackProvided && methodNameStr == "toString") { - POP_LOCAL_JAVA_FRAME(); - return methodCallSync(args); - } - - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - - jobject method = javaFindMethod(env, self->m_class, methodNameStr, methodArgs); - if(method == NULL) { - EXCEPTION_CALL_CALLBACK("Could not find method " << methodNameStr); - POP_LOCAL_JAVA_FRAME(); - return v8::Undefined(); - } - - // run - InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self->m_java, self, method, methodArgs, callback); - baton->run(); - - env->DeleteLocalRef(methodArgs); - env->DeleteLocalRef(method); - POP_LOCAL_JAVA_FRAME(); - - END_CALLBACK_FUNCTION("\"Method '" << methodNameStr << "' called without a callback did you mean to use the Sync version?\""); -} - -/*static*/ v8::Handle JavaObject::methodCallSync(const v8::Arguments& args) { - v8::HandleScope scope; - JavaObject* self = node::ObjectWrap::Unwrap(args.This()); - JNIEnv *env = self->m_java->getJavaEnv(); - - PUSH_LOCAL_JAVA_FRAME(); - - v8::String::AsciiValue methodName(args.Data()); - std::string methodNameStr = *methodName; - - int argsStart = 0; - int argsEnd = args.Length(); - - jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd); - - jobject method = javaFindMethod(env, self->m_class, methodNameStr, methodArgs); - if(method == NULL) { - std::ostringstream errStr; - errStr << "Could not find method " << methodNameStr; - v8::Handle ex = javaExceptionToV8(env, errStr.str()); - POP_LOCAL_JAVA_FRAME(); - return ThrowException(ex); - } - - // run - v8::Handle callback = v8::Object::New(); - InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self->m_java, self, method, methodArgs, callback); - v8::Handle result = baton->runSync(); - delete baton; - - POP_LOCAL_JAVA_FRAME(); - - return scope.Close(result); -} - -/*static*/ v8::Handle JavaObject::fieldGetter(v8::Local property, const v8::AccessorInfo& info) { - v8::HandleScope scope; - JavaObject* self = node::ObjectWrap::Unwrap(info.This()); - JNIEnv *env = self->m_java->getJavaEnv(); - - PUSH_LOCAL_JAVA_FRAME(); - - v8::String::AsciiValue propertyCStr(property); - std::string propertyStr = *propertyCStr; - jobject field = javaFindField(env, self->m_class, propertyStr); - if(field == NULL) { - std::ostringstream errStr; - errStr << "Could not find field " << propertyStr; - v8::Handle ex = javaExceptionToV8(env, errStr.str()); - POP_LOCAL_JAVA_FRAME(); - return ThrowException(ex); - } - - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_get = env->GetMethodID(fieldClazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); - - // get field value - jobject val = env->CallObjectMethod(field, field_get, self->m_obj); - if(env->ExceptionOccurred()) { - std::ostringstream errStr; - errStr << "Could not get field " << propertyStr; - v8::Handle ex = javaExceptionToV8(env, errStr.str()); - POP_LOCAL_JAVA_FRAME(); - return ThrowException(ex); - } - - v8::Handle result = javaToV8(self->m_java, env, val); - - env->DeleteLocalRef(fieldClazz); - env->DeleteLocalRef(field); - env->DeleteLocalRef(val); - POP_LOCAL_JAVA_FRAME(); - - return scope.Close(result); -} - -/*static*/ void JavaObject::fieldSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info) { - v8::HandleScope scope; - JavaObject* self = node::ObjectWrap::Unwrap(info.This()); - JNIEnv *env = self->m_java->getJavaEnv(); - - PUSH_LOCAL_JAVA_FRAME(); - - jobject newValue = v8ToJava(env, value); - - v8::String::AsciiValue propertyCStr(property); - std::string propertyStr = *propertyCStr; - jobject field = javaFindField(env, self->m_class, propertyStr); - if(field == NULL) { - std::ostringstream errStr; - errStr << "Could not find field " << propertyStr; - v8::Handle ex = javaExceptionToV8(env, errStr.str()); - POP_LOCAL_JAVA_FRAME(); - ThrowException(ex); - return; - } - - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_set = env->GetMethodID(fieldClazz, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - - //printf("newValue: %s\n", javaObjectToString(env, newValue).c_str()); - - // set field value - env->CallObjectMethod(field, field_set, self->m_obj, newValue); - if(env->ExceptionOccurred()) { - std::ostringstream errStr; - errStr << "Could not set field " << propertyStr; - v8::Handle ex = javaExceptionToV8(env, errStr.str()); - POP_LOCAL_JAVA_FRAME(); - ThrowException(ex); - return; - } - - POP_LOCAL_JAVA_FRAME(); -} diff --git a/src/javaObject.h b/src/javaObject.h deleted file mode 100644 index 7e5a9a22..00000000 --- a/src/javaObject.h +++ /dev/null @@ -1,37 +0,0 @@ - -#ifndef _javaobject_h_ -#define _javaobject_h_ - -#include -#include -#include -#include -#include "methodCallBaton.h" - -class Java; - -class JavaObject : public node::ObjectWrap { -public: - static void Init(v8::Handle target); - static v8::Local New(Java* java, jobject obj); - - jobject getObject() { return m_obj; } - - void Ref() { node::ObjectWrap::Ref(); } - void Unref() { node::ObjectWrap::Unref(); } - -private: - JavaObject(Java* java, jobject obj); - ~JavaObject(); - static v8::Handle methodCall(const v8::Arguments& args); - static v8::Handle methodCallSync(const v8::Arguments& args); - static v8::Handle fieldGetter(v8::Local property, const v8::AccessorInfo& info); - static void fieldSetter(v8::Local property, v8::Local value, const v8::AccessorInfo& info); - - static v8::Persistent s_ct; - Java* m_java; - jobject m_obj; - jclass m_class; -}; - -#endif diff --git a/src/methodCallBaton.cpp b/src/methodCallBaton.cpp deleted file mode 100644 index f0b3d273..00000000 --- a/src/methodCallBaton.cpp +++ /dev/null @@ -1,195 +0,0 @@ - -#include "methodCallBaton.h" -#include "java.h" -#include "javaObject.h" - -MethodCallBaton::MethodCallBaton(Java* java, jobject method, jarray args, v8::Handle& callback) { - JNIEnv *env = java->getJavaEnv(); - - m_java = java; - m_args = (jarray)env->NewGlobalRef(args); - m_callback = v8::Persistent::New(callback); - m_method = env->NewGlobalRef(method); - m_error = NULL; - m_result = NULL; -} - -MethodCallBaton::~MethodCallBaton() { - JNIEnv *env = m_java->getJavaEnv(); - env->DeleteGlobalRef(m_args); - env->DeleteGlobalRef(m_method); - m_callback.Dispose(); -} - -void MethodCallBaton::run() { - uv_work_t* req = new uv_work_t(); - req->data = this; - uv_queue_work(uv_default_loop(), req, MethodCallBaton::EIO_MethodCall, MethodCallBaton::EIO_AfterMethodCall); -} - -v8::Handle MethodCallBaton::runSync() { - JNIEnv *env = m_java->getJavaEnv(); - execute(env); - return resultsToV8(env); -} - -/*static*/ void MethodCallBaton::EIO_MethodCall(uv_work_t* req) { - MethodCallBaton* self = static_cast(req->data); - JNIEnv *env = javaAttachCurrentThread(self->m_java->getJvm()); - self->execute(env); - javaDetachCurrentThread(self->m_java->getJvm()); -} - -/*static*/ void MethodCallBaton::EIO_AfterMethodCall(uv_work_t* req) { - MethodCallBaton* self = static_cast(req->data); - JNIEnv *env = self->m_java->getJavaEnv(); - self->after(env); - delete req; - delete self; -} - -void MethodCallBaton::after(JNIEnv *env) { - if(m_callback->IsFunction()) { - v8::Handle result = resultsToV8(env); - v8::Handle argv[2]; - if(result->IsNativeError()) { - argv[0] = result; - argv[1] = v8::Undefined(); - } else { - argv[0] = v8::Undefined(); - argv[1] = result; - } - v8::Function::Cast(*m_callback)->Call(v8::Context::GetCurrent()->Global(), 2, argv); - } - - if(m_result) { - env->DeleteGlobalRef(m_result); - } -} - -v8::Handle MethodCallBaton::resultsToV8(JNIEnv *env) { - v8::HandleScope scope; - - if(m_error) { - v8::Handle err = javaExceptionToV8(env, m_error, m_errorString); - env->DeleteGlobalRef(m_error); - return scope.Close(err); - } - - return scope.Close(javaToV8(m_java, env, m_result)); -} - -void NewInstanceBaton::execute(JNIEnv *env) { - jclass constructorClazz = env->FindClass("java/lang/reflect/Constructor"); - jmethodID constructor_newInstance = env->GetMethodID(constructorClazz, "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"); - - //printf("invoke: %s\n", javaObjectToString(env, m_method).c_str()); - - jobject result = env->CallObjectMethod(m_method, constructor_newInstance, m_args); - jthrowable err = env->ExceptionOccurred(); - if(err) { - m_error = (jthrowable)env->NewGlobalRef(err); - m_errorString = "Error creating class"; - env->ExceptionClear(); - return; - } - - m_result = env->NewGlobalRef(result); -} - -void StaticMethodCallBaton::execute(JNIEnv *env) { - jclass methodClazz = env->FindClass("java/lang/reflect/Method"); - jmethodID method_invoke = env->GetMethodID(methodClazz, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); - - /* - printf("calling %s\n", javaObjectToString(env, m_method).c_str()); - printf("arguments\n"); - for(int i=0; iGetArrayLength(m_args); i++) { - printf(" %s\n", javaObjectToString(env, env->GetObjectArrayElement((jobjectArray)m_args, i)).c_str()); - } - */ - - jobject result = env->CallObjectMethod(m_method, method_invoke, NULL, m_args); - - jthrowable err = env->ExceptionOccurred(); - if(err) { - m_error = (jthrowable)env->NewGlobalRef(err); - m_errorString = "Error running static method"; - env->ExceptionClear(); - return; - } - - m_result = env->NewGlobalRef(result); -} - -void InstanceMethodCallBaton::execute(JNIEnv *env) { - jclass methodClazz = env->FindClass("java/lang/reflect/Method"); - jmethodID method_invoke = env->GetMethodID(methodClazz, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); - - /* - printf("calling %s\n", javaObjectToString(env, m_method).c_str()); - printf("arguments\n"); - for(int i=0; iGetArrayLength(m_args); i++) { - printf(" %s\n", javaObjectToString(env, env->GetObjectArrayElement((jobjectArray)m_args, i)).c_str()); - } - */ - - jobject result = env->CallObjectMethod(m_method, method_invoke, m_javaObject->getObject(), m_args); - - env->DeleteLocalRef(methodClazz); - - jthrowable err = env->ExceptionOccurred(); - if(err) { - m_error = (jthrowable)env->NewGlobalRef(err); - m_errorString = "Error running instance method"; - env->ExceptionClear(); - return; - } - - m_result = env->NewGlobalRef(result); - env->DeleteLocalRef(result); -} - -NewInstanceBaton::NewInstanceBaton( - Java* java, - jclass clazz, - jobject method, - jarray args, - v8::Handle& callback) : MethodCallBaton(java, method, args, callback) { - JNIEnv *env = m_java->getJavaEnv(); - m_clazz = (jclass)env->NewGlobalRef(clazz); -} - -NewInstanceBaton::~NewInstanceBaton() { - JNIEnv *env = m_java->getJavaEnv(); - env->DeleteGlobalRef(m_clazz); -} - -StaticMethodCallBaton::StaticMethodCallBaton( - Java* java, - jclass clazz, - jobject method, - jarray args, - v8::Handle& callback) : MethodCallBaton(java, method, args, callback) { - JNIEnv *env = m_java->getJavaEnv(); - m_clazz = (jclass)env->NewGlobalRef(clazz); -} - -StaticMethodCallBaton::~StaticMethodCallBaton() { - JNIEnv *env = m_java->getJavaEnv(); - env->DeleteGlobalRef(m_clazz); -} - -InstanceMethodCallBaton::InstanceMethodCallBaton( - Java* java, - JavaObject* obj, - jobject method, - jarray args, - v8::Handle& callback) : MethodCallBaton(java, method, args, callback) { - m_javaObject = obj; - m_javaObject->Ref(); -} - -InstanceMethodCallBaton::~InstanceMethodCallBaton() { - m_javaObject->Unref(); -} diff --git a/src/methodCallBaton.h b/src/methodCallBaton.h deleted file mode 100644 index 2da52335..00000000 --- a/src/methodCallBaton.h +++ /dev/null @@ -1,71 +0,0 @@ - -#ifndef _methodcallbaton_h_ -#define _methodcallbaton_h_ - -#include "utils.h" -#include -#include -#include -#include - -class Java; -class JavaObject; - -class MethodCallBaton { -public: - MethodCallBaton(Java* java, jobject method, jarray args, v8::Handle& callback); - virtual ~MethodCallBaton(); - - static void EIO_MethodCall(uv_work_t* req); - static void EIO_AfterMethodCall(uv_work_t* req); - void run(); - v8::Handle runSync(); - -protected: - virtual void execute(JNIEnv *env) = 0; - virtual void after(JNIEnv *env); - v8::Handle resultsToV8(JNIEnv *env); - - Java* m_java; - v8::Persistent m_callback; - jthrowable m_error; - std::string m_errorString; - jarray m_args; - jobject m_result; - jobject m_method; -}; - -class InstanceMethodCallBaton : public MethodCallBaton { -public: - InstanceMethodCallBaton(Java* java, JavaObject* obj, jobject method, jarray args, v8::Handle& callback); - virtual ~InstanceMethodCallBaton(); - -protected: - virtual void execute(JNIEnv *env); - - JavaObject* m_javaObject; -}; - -class NewInstanceBaton : public MethodCallBaton { -public: - NewInstanceBaton(Java* java, jclass clazz, jobject method, jarray args, v8::Handle& callback); - virtual ~NewInstanceBaton(); - -protected: - virtual void execute(JNIEnv *env); - - jclass m_clazz; -}; - -class StaticMethodCallBaton : public MethodCallBaton { -public: - StaticMethodCallBaton(Java* java, jclass clazz, jobject method, jarray args, v8::Handle& callback); - virtual ~StaticMethodCallBaton(); - -protected: - virtual void execute(JNIEnv *env); - - jclass m_clazz; -}; - -#endif diff --git a/src/nodeJavaBridge.cpp b/src/nodeJavaBridge.cpp deleted file mode 100644 index b98d1496..00000000 --- a/src/nodeJavaBridge.cpp +++ /dev/null @@ -1,20 +0,0 @@ - -#include "java.h" -#include "javaObject.h" - -extern "C" { - static void init(v8::Handle target) { - Java::Init(target); - JavaObject::Init(target); - } - - NODE_MODULE(nodejavabridge_bindings, init); -} - -#ifdef WIN32 - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { - return TRUE; -} - -#endif \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index ded1bc47..00000000 --- a/src/utils.cpp +++ /dev/null @@ -1,460 +0,0 @@ - -#include "utils.h" -#include -#include -#include -#include "javaObject.h" -#include "java.h" - -#define MODIFIER_STATIC 9 - -void javaReflectionGetMethods(JNIEnv *env, jclass clazz, std::list* methods) { - jclass clazzclazz = env->GetObjectClass(clazz); - jmethodID clazz_getMethods = env->GetMethodID(clazzclazz, "getMethods", "()[Ljava/lang/reflect/Method;"); - jclass methodClazz = env->FindClass("java/lang/reflect/Method"); - jmethodID method_getModifiers = env->GetMethodID(methodClazz, "getModifiers", "()I"); - - jobjectArray methodObjects = (jobjectArray)env->CallObjectMethod(clazz, clazz_getMethods); - jsize methodCount = env->GetArrayLength(methodObjects); - for(jsize i=0; iGetObjectArrayElement(methodObjects, i); - jint methodModifiers = env->CallIntMethod(method, method_getModifiers); - if((methodModifiers & MODIFIER_STATIC) == MODIFIER_STATIC) { - env->DeleteLocalRef(method); - continue; - } - methods->push_back(method); - } - env->DeleteLocalRef(methodObjects); -} - -void javaReflectionGetFields(JNIEnv *env, jclass clazz, std::list* fields) { - jclass clazzclazz = env->GetObjectClass(clazz); - jmethodID clazz_getFields = env->GetMethodID(clazzclazz, "getFields", "()[Ljava/lang/reflect/Field;"); - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_getModifiers = env->GetMethodID(fieldClazz, "getModifiers", "()I"); - - jobjectArray fieldObjects = (jobjectArray)env->CallObjectMethod(clazz, clazz_getFields); - jsize fieldCount = env->GetArrayLength(fieldObjects); - for(jsize i=0; iGetObjectArrayElement(fieldObjects, i); - jint fieldModifiers = env->CallIntMethod(field, field_getModifiers); - if((fieldModifiers & MODIFIER_STATIC) == MODIFIER_STATIC) { - env->DeleteLocalRef(field); - continue; - } - fields->push_back(field); - } - env->DeleteLocalRef(fieldObjects); -} - -std::string javaToString(JNIEnv *env, jstring str) { - const char* chars = env->GetStringUTFChars(str, NULL); - std::string results = chars; - env->ReleaseStringUTFChars(str, chars); - return results; -} - -std::string javaObjectToString(JNIEnv *env, jobject obj) { - if(obj == NULL) { - return "(null)"; - } - jclass objClazz = env->GetObjectClass(obj); - jmethodID methodId = env->GetMethodID(objClazz, "toString", "()Ljava/lang/String;"); - jstring result = (jstring)env->CallObjectMethod(obj, methodId); - return javaToString(env, result); -} - -JNIEnv* javaAttachCurrentThread(JavaVM* jvm) { - JNIEnv* env; - JavaVMAttachArgs attachArgs; - attachArgs.version = JNI_VERSION_1_4; - attachArgs.name = NULL; - attachArgs.group = NULL; - jvm->AttachCurrentThread((void**)&env, &attachArgs); - return env; -} - -void javaDetachCurrentThread(JavaVM* jvm) { - jvm->DetachCurrentThread(); -} - -jvalueType javaGetType(JNIEnv *env, jclass type) { - jclass clazzClazz = env->FindClass("java/lang/Class"); - jmethodID class_isArray = env->GetMethodID(clazzClazz, "isArray", "()Z"); - - jboolean isArray = env->CallBooleanMethod(type, class_isArray); - if(isArray) { - return TYPE_ARRAY; - } else { - // TODO: has to be a better way - std::string str = javaObjectToString(env, type); - const char *typeStr = str.c_str(); - //printf("javaGetType: %s\n", typeStr); - if(strcmp(typeStr, "void") == 0) { - return TYPE_VOID; - } else if(strcmp(typeStr, "int") == 0 || strcmp(typeStr, "class java.lang.Integer") == 0) { - return TYPE_INT; - } else if(strcmp(typeStr, "double") == 0 || strcmp(typeStr, "class java.lang.Double") == 0) { - return TYPE_DOUBLE; - } else if(strcmp(typeStr, "long") == 0 || strcmp(typeStr, "class java.lang.Long") == 0) { - return TYPE_LONG; - } else if(strcmp(typeStr, "boolean") == 0 || strcmp(typeStr, "class java.lang.Boolean") == 0) { - return TYPE_BOOLEAN; - } else if(strcmp(typeStr, "byte") == 0 || strcmp(typeStr, "class java.lang.Byte") == 0) { - return TYPE_BYTE; - } else if(strcmp(typeStr, "class java.lang.String") == 0) { - return TYPE_STRING; - } - return TYPE_OBJECT; - } -} - -jclass javaFindClass(JNIEnv* env, std::string& className) { - std::string searchClassName = className; - std::replace(searchClassName.begin(), searchClassName.end(), '.', '/'); - -// Alternate find class trying to fix Class.forName -// jclass threadClazz = env->FindClass("java/lang/Thread"); -// jmethodID thread_getCurrentThread = env->GetStaticMethodID(threadClazz, "currentThread", "()Ljava/lang/Thread;"); -// jmethodID thread_getContextClassLoader = env->GetMethodID(threadClazz, "getContextClassLoader", "()Ljava/lang/ClassLoader;"); -// -// jclass classLoaderClazz = env->FindClass("java/lang/ClassLoader"); -// jmethodID classLoader_loadClass = env->GetMethodID(classLoaderClazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); -// -// jobject currentThread = env->CallObjectMethod(threadClazz, thread_getCurrentThread); -// jobject classLoader = env->CallObjectMethod(currentThread, thread_getContextClassLoader); -// jstring searchClassNameJava = env->NewStringUTF(className.c_str()); -// jclass clazz = (jclass)env->CallObjectMethod(classLoader, classLoader_loadClass, searchClassNameJava); - - jclass clazz = env->FindClass(searchClassName.c_str()); - return clazz; -} - -jobject javaFindField(JNIEnv* env, jclass clazz, std::string& fieldName) { - jobject result = NULL; - jclass clazzclazz = env->GetObjectClass(clazz); - jclass fieldClazz = env->FindClass("java/lang/reflect/Field"); - jmethodID field_getName = env->GetMethodID(fieldClazz, "getName", "()Ljava/lang/String;"); - jmethodID class_getFields = env->GetMethodID(clazzclazz, "getFields", "()[Ljava/lang/reflect/Field;"); - jobjectArray fieldObjects = (jobjectArray)env->CallObjectMethod(clazz, class_getFields); - - jsize fieldCount = env->GetArrayLength(fieldObjects); - for(jsize i=0; iGetObjectArrayElement(fieldObjects, i); - jstring fieldNameJava = (jstring)env->CallObjectMethod(field, field_getName); - std::string itFieldName = javaToString(env, fieldNameJava); - env->DeleteLocalRef(fieldNameJava); - if(strcmp(itFieldName.c_str(), fieldName.c_str()) == 0) { - result = field; - break; - } - env->DeleteLocalRef(field); - } - - env->DeleteLocalRef(fieldObjects); - env->DeleteLocalRef(clazzclazz); - env->DeleteLocalRef(fieldClazz); - return result; -} - -jobject v8ToJava(JNIEnv* env, v8::Local arg) { - if(arg.IsEmpty() || arg->IsNull() || arg->IsUndefined()) { - return NULL; - } - - if(arg->IsArray()) { - v8::Local array = v8::Array::Cast(*arg); - uint32_t arraySize = array->Length(); - jclass objectClazz = env->FindClass("java/lang/Object"); - jobjectArray result = env->NewObjectArray(arraySize, objectClazz, NULL); - for(uint32_t i=0; iGet(i)); - env->SetObjectArrayElement(result, i, val); - } - return result; - } - - if(arg->IsString()) { - v8::String::AsciiValue val(arg->ToString()); - return env->NewStringUTF(*val); - } - - if(arg->IsInt32() || arg->IsUint32()) { - jint val = arg->ToInt32()->Value(); - jclass clazz = env->FindClass("java/lang/Integer"); - jmethodID constructor = env->GetMethodID(clazz, "", "(I)V"); - return env->NewObject(clazz, constructor, val); - } - - if(arg->IsNumber()) { - jdouble val = arg->ToNumber()->Value(); - jclass clazz = env->FindClass("java/lang/Double"); - jmethodID constructor = env->GetMethodID(clazz, "", "(D)V"); - return env->NewObject(clazz, constructor, val); - } - - if(arg->IsBoolean()) { - jboolean val = arg->ToBoolean()->Value(); - jclass clazz = env->FindClass("java/lang/Boolean"); - jmethodID constructor = env->GetMethodID(clazz, "", "(Z)V"); - return env->NewObject(clazz, constructor, val); - } - - if(arg->IsObject()) { - v8::Local obj = v8::Object::Cast(*arg); - v8::String::AsciiValue constructorName(obj->GetConstructorName()); - if(strcmp(*constructorName, "JavaObject") == 0) { - JavaObject* javaObject = node::ObjectWrap::Unwrap(obj); - jobject jobj = javaObject->getObject(); - jclass nodeDynamicProxyClass = env->FindClass("node/NodeDynamicProxyClass"); - - if(env->IsInstanceOf(jobj, nodeDynamicProxyClass)) { - jfieldID ptrField = env->GetFieldID(nodeDynamicProxyClass, "ptr", "J"); - DynamicProxyData* proxyData = (DynamicProxyData*)(long)env->GetLongField(jobj, ptrField); - if(!dynamicProxyDataVerify(proxyData)) { - return NULL; - } - - jclass dynamicInterface = javaFindClass(env, proxyData->interfaceName); - if(dynamicInterface == NULL) { - printf("Could not find interface %s\n", proxyData->interfaceName.c_str()); - return NULL; - } - jclass classClazz = env->FindClass("java/lang/Class"); - jobjectArray classArray = env->NewObjectArray(1, classClazz, NULL); - env->SetObjectArrayElement(classArray, 0, dynamicInterface); - - jmethodID class_getClassLoader = env->GetMethodID(classClazz, "getClassLoader", "()Ljava/lang/ClassLoader;"); - jobject classLoader = env->CallObjectMethod(dynamicInterface, class_getClassLoader); - if(classLoader == NULL) { - jclass objectClazz = env->FindClass("java/lang/Object"); - jmethodID object_getClass = env->GetMethodID(classClazz, "getClass", "()Ljava/lang/Class;"); - jobject jobjClass = env->CallObjectMethod(jobj, object_getClass); - classLoader = env->CallObjectMethod(jobjClass, class_getClassLoader); - } - - jclass proxyClass = env->FindClass("java/lang/reflect/Proxy"); - jmethodID proxy_newProxyInstance = env->GetStaticMethodID(proxyClass, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;"); - if(classLoader == NULL) { - printf("Could not get classloader for Proxy\n"); - return NULL; - } - if(classArray == NULL) { - printf("Could not create class array for Proxy\n"); - return NULL; - } - if(jobj == NULL) { - printf("Not a valid object to wrap\n"); - return NULL; - } - jobj = env->CallStaticObjectMethod(proxyClass, proxy_newProxyInstance, classLoader, classArray, jobj); - } - - return jobj; - } - } - - // TODO: handle other arg types - v8::String::AsciiValue typeStr(arg); - printf("Unhandled type: %s\n", *typeStr); - return NULL; -} - -jobjectArray v8ToJava(JNIEnv* env, const v8::Arguments& args, int start, int end) { - jclass clazz = env->FindClass("java/lang/Object"); - jobjectArray results = env->NewObjectArray(end-start, clazz, NULL); - - for(int i=start; iSetObjectArrayElement(results, i - start, val); - env->DeleteLocalRef(val); - } - env->DeleteLocalRef(clazz); - - return results; -} - -v8::Handle javaExceptionToV8(JNIEnv* env, jthrowable ex, const std::string& alternateMessage) { - v8::HandleScope scope; - - std::ostringstream msg; - msg << alternateMessage; - - if(ex) { - jclass stringWriterClazz = env->FindClass("java/io/StringWriter"); - jmethodID stringWriter_constructor = env->GetMethodID(stringWriterClazz, "", "()V"); - jmethodID stringWriter_toString = env->GetMethodID(stringWriterClazz, "toString", "()Ljava/lang/String;"); - jobject stringWriter = env->NewObject(stringWriterClazz, stringWriter_constructor); - - jclass printWriterClazz = env->FindClass("java/io/PrintWriter"); - jmethodID printWriter_constructor = env->GetMethodID(printWriterClazz, "", "(Ljava/io/Writer;)V"); - jobject printWriter = env->NewObject(printWriterClazz, printWriter_constructor, stringWriter); - - jclass throwableClazz = env->FindClass("java/lang/Throwable"); - jmethodID throwable_printStackTrace = env->GetMethodID(throwableClazz, "printStackTrace", "(Ljava/io/PrintWriter;)V"); - env->CallObjectMethod(ex, throwable_printStackTrace, printWriter); - - jstring strObj = (jstring)env->CallObjectMethod(stringWriter, stringWriter_toString); - std::string stackTrace = javaToString(env, strObj); - - msg << "\n" << stackTrace; - } - - return scope.Close(v8::Exception::Error(v8::String::New(msg.str().c_str()))); -} - -v8::Handle javaExceptionToV8(JNIEnv* env, const std::string& alternateMessage) { - v8::HandleScope scope; - jthrowable ex = env->ExceptionOccurred(); - env->ExceptionClear(); - return scope.Close(javaExceptionToV8(env, ex, alternateMessage)); -} - -v8::Handle javaArrayToV8(Java* java, JNIEnv* env, jobjectArray objArray) { - v8::HandleScope scope; - - if(objArray == NULL) { - return v8::Null(); - } - - //printf("javaArrayToV8: %d %s\n", javaObjectToString(env, objArray).c_str()); - - jsize arraySize = env->GetArrayLength(objArray); - //printf("array size: %d\n", arraySize); - - v8::Handle result = v8::Array::New(arraySize); - for(jsize i=0; iGetObjectArrayElement(objArray, i); - v8::Handle item = javaToV8(java, env, obj); - result->Set(i, item); - } - - return scope.Close(result); -} - -v8::Handle javaToV8(Java* java, JNIEnv* env, jobject obj) { - v8::HandleScope scope; - PUSH_LOCAL_JAVA_FRAME(); - - if(obj == NULL) { - POP_LOCAL_JAVA_FRAME_AND_RETURN(v8::Null()); - } - - jclass objClazz = env->GetObjectClass(obj); - jvalueType resultType = javaGetType(env, objClazz); - - //printf("javaToV8: %d %s\n", resultType, javaObjectToString(env, obj).c_str()); - - switch(resultType) { - case TYPE_ARRAY: - { - v8::Handle result = javaArrayToV8(java, env, (jobjectArray)obj); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(result)); - } - case TYPE_VOID: - POP_LOCAL_JAVA_FRAME_AND_RETURN(v8::Undefined()); - case TYPE_BOOLEAN: - { - jclass booleanClazz = env->FindClass("java/lang/Boolean"); - jmethodID boolean_booleanValue = env->GetMethodID(booleanClazz, "booleanValue", "()Z"); - bool result = env->CallBooleanMethod(obj, boolean_booleanValue); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::Boolean::New(result))); - } - case TYPE_BYTE: - { - jclass byteClazz = env->FindClass("java/lang/Byte"); - jmethodID byte_byteValue = env->GetMethodID(byteClazz, "byteValue", "()B"); - jbyte result = env->CallByteMethod(obj, byte_byteValue); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::Number::New(result))); - } - case TYPE_LONG: - { - jclass longClazz = env->FindClass("java/lang/Long"); - jmethodID long_longValue = env->GetMethodID(longClazz, "longValue", "()J"); - jlong result = env->CallLongMethod(obj, long_longValue); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::Number::New(result))); - } - case TYPE_INT: - { - jclass integerClazz = env->FindClass("java/lang/Integer"); - jmethodID integer_intValue = env->GetMethodID(integerClazz, "intValue", "()I"); - jint result = env->CallIntMethod(obj, integer_intValue); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::Integer::New(result))); - } - case TYPE_DOUBLE: - { - jclass doubleClazz = env->FindClass("java/lang/Double"); - jmethodID double_doubleValue = env->GetMethodID(doubleClazz, "doubleValue", "()D"); - jdouble result = env->CallDoubleMethod(obj, double_doubleValue); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::Number::New(result))); - } - case TYPE_STRING: - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(v8::String::New(javaObjectToString(env, obj).c_str()))); - case TYPE_OBJECT: - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(JavaObject::New(java, obj))); - default: - printf("unhandled type: 0x%03x\n", resultType); - POP_LOCAL_JAVA_FRAME_AND_RETURN(scope.Close(JavaObject::New(java, obj))); - } - - POP_LOCAL_JAVA_FRAME_AND_RETURN(v8::Undefined()); -} - -jobjectArray javaObjectArrayToClasses(JNIEnv *env, jobjectArray objs) { - jclass clazzClazz = env->FindClass("java/lang/Class"); - jsize objsLength = env->GetArrayLength(objs); - jobjectArray results = env->NewObjectArray(objsLength, clazzClazz, NULL); - for(jsize i=0; iGetObjectArrayElement(objs, i); - if(elem == NULL) { - env->SetObjectArrayElement(results, i, NULL); - } else { - jclass objClazz = env->GetObjectClass(elem); - env->SetObjectArrayElement(results, i, objClazz); - env->DeleteLocalRef(objClazz); - } - env->DeleteLocalRef(elem); - } - env->DeleteLocalRef(clazzClazz); - return results; -} - -jobject javaFindMethod(JNIEnv *env, jclass clazz, std::string& methodName, jobjectArray methodArgs) { - jclass methodUtilsClazz = env->FindClass("com/nearinfinity/org/apache/commons/lang3/reflect/MethodUtils"); - jmethodID methodUtils_getMatchingAccessibleMethod = env->GetStaticMethodID(methodUtilsClazz, "getMatchingAccessibleMethod", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); - const char *methodNameCStr = methodName.c_str(); - jstring methodNameJavaStr = env->NewStringUTF(methodNameCStr); - jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs); - jobject method = env->CallStaticObjectMethod(methodUtilsClazz, methodUtils_getMatchingAccessibleMethod, clazz, methodNameJavaStr, methodArgClasses); - - env->DeleteLocalRef(methodUtilsClazz); - env->DeleteLocalRef(methodNameJavaStr); - env->DeleteLocalRef(methodArgClasses); - - return method; -} - -jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs) { - jclass constructorUtilsClazz = env->FindClass("com/nearinfinity/org/apache/commons/lang3/reflect/ConstructorUtils"); - jmethodID constructorUtils_getMatchingAccessibleConstructor = env->GetStaticMethodID(constructorUtilsClazz, "getMatchingAccessibleConstructor", "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"); - jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs); - jobject method = env->CallStaticObjectMethod(constructorUtilsClazz, constructorUtils_getMatchingAccessibleConstructor, clazz, methodArgClasses); - - return method; -} - -jobject longToJavaLongObj(JNIEnv *env, long val) { - jclass longClass = env->FindClass("java/lang/Long"); - jmethodID constructor = env->GetMethodID(longClass, "", "(J)V"); - return env->NewObject(longClass, constructor, val); -} - -int dynamicProxyDataVerify(DynamicProxyData* data) { - if(data->markerStart == DYNAMIC_PROXY_DATA_MARKER_START && data->markerEnd == DYNAMIC_PROXY_DATA_MARKER_END) { - return 1; - } - - printf("*** ERROR: Lost reference to the dynamic proxy. You must maintain a reference in javascript land using ref() and unref(). ***\n"); - return 0; -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 7b381b6c..00000000 --- a/src/utils.h +++ /dev/null @@ -1,132 +0,0 @@ - -#ifndef _utils_h_ -#define _utils_h_ - -#define BUILDING_NODE_EXTENSION 1 -#include -#include -#include -#include -#include -#include - -class Java; - -typedef enum _jvalueType { - TYPE_VOID = 1, - TYPE_INT = 2, - TYPE_LONG = 3, - TYPE_OBJECT = 4, - TYPE_STRING = 5, - TYPE_BOOLEAN = 6, - TYPE_BYTE = 7, - TYPE_DOUBLE = 8, - TYPE_ARRAY = 9 -} jvalueType; - -struct DynamicProxyData { - unsigned int markerStart; - JNIEnv* env; - Java* java; - std::string interfaceName; - v8::Persistent functions; - std::string methodName; - jobjectArray args; - jobject result; - int done; - unsigned int markerEnd; -}; - -#define LOCAL_FRAME_SIZE 500 - -#define DYNAMIC_PROXY_DATA_MARKER_START 0x12345678 -#define DYNAMIC_PROXY_DATA_MARKER_END 0x87654321 - -int dynamicProxyDataVerify(DynamicProxyData* data); - -void javaReflectionGetMethods(JNIEnv *env, jclass clazz, std::list* methods); -void javaReflectionGetFields(JNIEnv *env, jclass clazz, std::list* fields); -std::string javaToString(JNIEnv *env, jstring str); -std::string javaObjectToString(JNIEnv *env, jobject obj); -JNIEnv* javaAttachCurrentThread(JavaVM* jvm); -void javaDetachCurrentThread(JavaVM* jvm); -jvalueType javaGetType(JNIEnv *env, jclass type); -jobjectArray v8ToJava(JNIEnv* env, const v8::Arguments& args, int start, int end); -jobject v8ToJava(JNIEnv* env, v8::Local arg); -v8::Handle javaExceptionToV8(JNIEnv* env, const std::string& alternateMessage); -v8::Handle javaExceptionToV8(JNIEnv* env, jthrowable ex, const std::string& alternateMessage); -v8::Handle javaArrayToV8(Java* java, JNIEnv* env, jobjectArray objArray); -v8::Handle javaToV8(Java* java, JNIEnv* env, jobject obj); -jobjectArray javaObjectArrayToClasses(JNIEnv *env, jobjectArray objs); -jobject longToJavaLongObj(JNIEnv *env, long l); - -jclass javaFindClass(JNIEnv* env, std::string& className); -jobject javaFindField(JNIEnv* env, jclass clazz, std::string& fieldName); -jobject javaFindMethod(JNIEnv *env, jclass clazz, std::string& methodName, jobjectArray methodArgs); -jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs); - -#define UNUSED_VARIABLE(var) var = var; - -#define ARGS_FRONT_OBJECT(ARGNAME) \ - if(args.Length() < argsStart+1 || !args[argsStart]->IsObject()) { \ - std::ostringstream errStr; \ - errStr << "Argument " << (argsStart+1) << " must be an object"; \ - return ThrowException(v8::Exception::TypeError(v8::String::New(errStr.str().c_str()))); \ - } \ - v8::Local ARGNAME = v8::Local::Cast(args[argsStart]); \ - argsStart++; - -#define ARGS_FRONT_STRING(ARGNAME) \ - if(args.Length() < argsStart+1 || !args[argsStart]->IsString()) { \ - std::ostringstream errStr; \ - errStr << "Argument " << (argsStart+1) << " must be a string"; \ - return ThrowException(v8::Exception::TypeError(v8::String::New(errStr.str().c_str()))); \ - } \ - v8::Local _##ARGNAME##_obj = v8::Local::Cast(args[argsStart]); \ - v8::String::AsciiValue _##ARGNAME##_val(_##ARGNAME##_obj); \ - std::string ARGNAME = *_##ARGNAME##_val; \ - argsStart++; - -#define ARGS_FRONT_CLASSNAME() ARGS_FRONT_STRING(className) - -#define ARGS_BACK_CALLBACK() \ - bool callbackProvided; \ - v8::Handle callback; \ - if(args[args.Length()-1]->IsFunction()) { \ - callback = args[argsEnd-1]; \ - argsEnd--; \ - callbackProvided = true; \ - } else { \ - callback = v8::Null(); \ - callbackProvided = false; \ - } - -#define EXCEPTION_CALL_CALLBACK(STRBUILDER) \ - std::ostringstream errStr; \ - errStr << STRBUILDER; \ - v8::Handle error = javaExceptionToV8(env, errStr.str()); \ - v8::Handle argv[2]; \ - argv[0] = error; \ - argv[1] = v8::Undefined(); \ - v8::Function::Cast(*callback)->Call(v8::Context::GetCurrent()->Global(), 2, argv); - -#define END_CALLBACK_FUNCTION(MSG) \ - if(callbackProvided) { \ - return v8::Undefined(); \ - } else { \ - std::ostringstream str; \ - str << MSG; \ - return scope.Close(v8::String::New(str.str().c_str())); \ - } - -#define PUSH_LOCAL_JAVA_FRAME() \ - env->PushLocalFrame(LOCAL_FRAME_SIZE); - -#define POP_LOCAL_JAVA_FRAME() \ - env->PopLocalFrame(NULL); - -#define POP_LOCAL_JAVA_FRAME_AND_RETURN(r) \ - env->PopLocalFrame(NULL); \ - return r; - -#endif diff --git a/test/ListenerInterface.class b/test/ListenerInterface.class new file mode 100644 index 00000000..88929b2c Binary files /dev/null and b/test/ListenerInterface.class differ diff --git a/test/ListenerInterface.java b/test/ListenerInterface.java new file mode 100644 index 00000000..b4a53892 --- /dev/null +++ b/test/ListenerInterface.java @@ -0,0 +1,3 @@ +public interface ListenerInterface { + void onEvent(java.util.ArrayList list, java.lang.Runtime runtime); +} \ No newline at end of file diff --git a/test/ListenerTester.class b/test/ListenerTester.class new file mode 100644 index 00000000..0fe29fa5 Binary files /dev/null and b/test/ListenerTester.class differ diff --git a/test/ListenerTester.java b/test/ListenerTester.java new file mode 100644 index 00000000..f821d536 --- /dev/null +++ b/test/ListenerTester.java @@ -0,0 +1,19 @@ +public class ListenerTester { + private ListenerInterface _listener; + + public void setListener(ListenerInterface listener) { + this._listener = listener; + } + + public void raiseEvent() { + if(this._listener == null) + return; + + java.util.ArrayList list = new java.util.ArrayList(); + list.add("hello"); + list.add("from"); + list.add("Java"); + + this._listener.onEvent(list, java.lang.Runtime.getRuntime()); + } +} \ No newline at end of file diff --git a/test/RunInterface$1.class b/test/RunInterface$1.class new file mode 100644 index 00000000..12eae837 Binary files /dev/null and b/test/RunInterface$1.class differ diff --git a/test/RunInterface$Interface0Arg.class b/test/RunInterface$Interface0Arg.class index e4ce043c..907fdae8 100644 Binary files a/test/RunInterface$Interface0Arg.class and b/test/RunInterface$Interface0Arg.class differ diff --git a/test/RunInterface$Interface1Arg.class b/test/RunInterface$Interface1Arg.class index 7d5bc719..2008751c 100644 Binary files a/test/RunInterface$Interface1Arg.class and b/test/RunInterface$Interface1Arg.class differ diff --git a/test/RunInterface$InterfaceWithReturn.class b/test/RunInterface$InterfaceWithReturn.class index b2a0794a..6257cc81 100644 Binary files a/test/RunInterface$InterfaceWithReturn.class and b/test/RunInterface$InterfaceWithReturn.class differ diff --git a/test/RunInterface.class b/test/RunInterface.class index 77d3d2e1..1404e1a2 100644 Binary files a/test/RunInterface.class and b/test/RunInterface.class differ diff --git a/test/RunInterface.java b/test/RunInterface.java index 7681e3c4..94c1758a 100644 --- a/test/RunInterface.java +++ b/test/RunInterface.java @@ -1,3 +1,4 @@ +import java.util.concurrent.CountDownLatch; public class RunInterface { public static interface Interface0Arg { @@ -23,6 +24,47 @@ public void run1Args(Interface1Arg r) { } public int runWithReturn(InterfaceWithReturn r) { - return r.run(42); + try { + return r.run(42); + } catch (RuntimeException e) { + throw new RuntimeException(e); + } + } + + public int runInAnotherThread(final InterfaceWithReturn r) throws InterruptedException { + final int[] result = {0}; + final CountDownLatch latch = new CountDownLatch(1); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + result[0] = r.run(46); + latch.countDown(); + } + }); + t.start(); + latch.await(); + return result[0]; + } + + public boolean runEquals(final InterfaceWithReturn r) { + return r.equals(Boolean.FALSE); + } + + public int runHashCode(final InterfaceWithReturn r) { + return r.hashCode(); + } + + private InterfaceWithReturn prev; + + public void setInstance(final InterfaceWithReturn r) { + prev = r; + } + + public boolean runEqualsInstance(final InterfaceWithReturn r) { + return r.equals(prev); + } + + public String runToString(final InterfaceWithReturn r) { + return r.toString(); } } diff --git a/test/Test$Enum.class b/test/Test$Enum.class new file mode 100644 index 00000000..5f851f69 Binary files /dev/null and b/test/Test$Enum.class differ diff --git a/test/Test$StaticEnum.class b/test/Test$StaticEnum.class new file mode 100644 index 00000000..a474b9f7 Binary files /dev/null and b/test/Test$StaticEnum.class differ diff --git a/test/Test$SubClass.class b/test/Test$SubClass.class index 637c5d24..dba56514 100644 Binary files a/test/Test$SubClass.class and b/test/Test$SubClass.class differ diff --git a/test/Test$SuperClass.class b/test/Test$SuperClass.class index a700925a..f3004faf 100644 Binary files a/test/Test$SuperClass.class and b/test/Test$SuperClass.class differ diff --git a/test/Test.class b/test/Test.class index 269d3938..87fd03f1 100644 Binary files a/test/Test.class and b/test/Test.class differ diff --git a/test/Test.java b/test/Test.java index 2378c2a2..d5ad6652 100644 --- a/test/Test.java +++ b/test/Test.java @@ -7,7 +7,8 @@ public class Test { public static Test[] staticArrayObjects = null; public Test() {} - public Test(int i) { this.i = i; } + public Test(Integer i) { this.i = i; } + public Test(Integer i, String... other) { this.i = i; } public int getInt() { return i; } @@ -18,11 +19,78 @@ public Test() {} public static String staticMethod() { return "staticMethod called"; } public static int staticMethod(int i) { return i + 1; } public static void staticMethodThrows(Exception ex) throws Exception { throw ex; } + public void methodThrows(Exception ex) throws Exception { throw ex; } + public static void staticMethodThrowsNewException() throws Exception { throw new Exception("my exception"); } + public void methodThrowsNewException() throws Exception { throw new Exception("my exception"); } public static int staticMethodOverload(String a) { return 1; } public static int staticMethodOverload(int a) { return 2; } public static int staticMethodOverload(SuperClass a) { return a.getVal(); } + public static String staticMethodCharArrayToString(char[] a) { return new String(a); } + public static String staticMethodLongToString(java.lang.Long l) { return l.toString(); } + public static long staticMethodReturnLong() { return java.lang.Long.MAX_VALUE; } + + public static boolean static2Objects(Object o1, Object o2) { return o1.equals(o2); } + + public static int staticByte(byte b) { return (int)b; } + public static int staticShort(short s) { return (int)s; } + public static int staticLong(long l) { return (int)l; } + public static double staticDouble(double s) { return s; } + public static float staticFloat(float s) { return s; } + public static String staticString(String s) { return s; } + + public static int staticMethodAmbiguous(Double a) { return 1; } + public static int staticMethodAmbiguous(Integer a) { return 2; } + + public int methodAmbiguous(Double a) { return 1; } + public int methodAmbiguous(Integer a) { return 2; } + + public static String staticVarargs(Integer i, String... args) { + java.lang.StringBuilder result = new java.lang.StringBuilder(); + result.append(i); + for(String arg : args) { + result.append(arg); + } + return result.toString(); + } + + public static String staticBigDecimalToString(java.math.BigDecimal bigDecimal) { return bigDecimal.toString(); } + + public static int staticChar(char ch) { return (int)ch; } + public static short[] staticShortArray(Short[] arg) { + short[] b = new short[arg.length]; + for(int i=0; i a + b; + return addition.op(x, y); + } + + public int testLambdaSubtraction(Integer x, Integer y) { + IntegerMath subtraction = (a, b) -> a - b; + return subtraction.op(x, y); + } +} diff --git a/test/TestLambda.test.ts b/test/TestLambda.test.ts new file mode 100644 index 00000000..0b7a8ab8 --- /dev/null +++ b/test/TestLambda.test.ts @@ -0,0 +1,31 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { expectJavaError, getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("Java8", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("call methods of a class that uses lambda expressions", () => { + try { + const TestLambda = java.import("TestLambda"); + const lambda = new TestLambda(); + const sum = lambda.testLambdaAdditionSync(23, 42); + expect(sum).toBe(65); + const diff = lambda.testLambdaSubtractionSync(23, 42); + expect(diff).toBe(-19); + } catch (err) { + expectJavaError(err); + const unsupportedVersion = java.instanceOf(err.cause, "java.lang.UnsupportedClassVersionError"); + if (unsupportedVersion) { + console.log("JRE 1.8 not available"); + } else { + console.error("Java8 test failed with unknown error:", err); + throw err; + } + } + }); +}); diff --git a/test/awt-test.js b/test/awt-test.js deleted file mode 100644 index e0c4e3bb..00000000 --- a/test/awt-test.js +++ /dev/null @@ -1,18 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); -var path = require('path'); - -exports['AWT'] = nodeunit.testCase({ - "calling AWT method (see issue 1)": function(test) { - var headlessVal = java.callStaticMethodSync("java.lang.System", "getProperty", "java.awt.headless"); - console.log("java.awt.headless =", headlessVal); - test.equal(headlessVal, 'true'); - var filename = path.join(path.dirname(__filename), "nodejs.png"); - var file = java.newInstanceSync("java.io.File", filename); - var image = java.callStaticMethodSync("javax.imageio.ImageIO", "read", file); - test.done(); - } -}); diff --git a/test/awt.test.ts b/test/awt.test.ts new file mode 100644 index 00000000..9c8a0520 --- /dev/null +++ b/test/awt.test.ts @@ -0,0 +1,22 @@ +import path from "node:path"; +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("AWT", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("calling AWT method (see issue 1)", () => { + const headlessVal = java.callStaticMethodSync("java.lang.System", "getProperty", "java.awt.headless"); + console.log("java.awt.headless =", headlessVal); + expect(headlessVal).toBe("true"); + const filename = path.join(path.dirname(__filename), "nodejs.png"); + const file = java.newInstanceSync("java.io.File", filename); + const image = java.callStaticMethodSync("javax.imageio.ImageIO", "read", file); + expect(image).toBeTruthy(); + }); +}); diff --git a/test/commons-lang3-3.1.jar b/test/commons-lang3-3.1.jar deleted file mode 100644 index a85e539b..00000000 Binary files a/test/commons-lang3-3.1.jar and /dev/null differ diff --git a/test/commons-lang3-3.18.0.jar b/test/commons-lang3-3.18.0.jar new file mode 100644 index 00000000..9359e524 Binary files /dev/null and b/test/commons-lang3-3.18.0.jar differ diff --git a/test/dynamicProxy-test.js b/test/dynamicProxy-test.js deleted file mode 100644 index 81d62dff..00000000 --- a/test/dynamicProxy-test.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -var java = require("../testHelpers").java; -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Dynamic Proxy'] = nodeunit.testCase({ - "0 Arguments": function (test) { - var callCount = 0; - - var myProxy = java.newProxy('RunInterface$Interface0Arg', { - run: function () { - callCount++; - } - }); - - var runInterface = java.newInstanceSync("RunInterface"); - runInterface.run0ArgsSync(myProxy); - - test.equals(callCount, 2); - - test.done(); - }, - - "1 Arguments": function (test) { - var runData = ''; - - var myProxy = java.newProxy('RunInterface$Interface1Arg', { - run: function (str) { - runData += str; - } - }); - - var runInterface = java.newInstanceSync("RunInterface"); - runInterface.run1ArgsSync(myProxy); - - test.equals(runData, 'test1test1'); - - test.done(); - }, - - "1 Arguments with return data": function (test) { - var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', { - run: function (i) { - return i + 1; - } - }); - - var runInterface = java.newInstanceSync("RunInterface"); - var result = runInterface.runWithReturnSync(myProxy); - - test.equals(result, 43); - - test.done(); - }, - - "thread": function (test) { - var callCount = 0; - - var myProxy = java.newProxy('java.lang.Runnable', { - run: function () { - callCount++; - } - }); - - var thread = java.newInstanceSync("java.lang.Thread", myProxy); - thread.startSync(); - - var timeout = 50; - - function waitForThread() { - if (callCount === 1) { - return test.done(); - } - timeout--; - if (timeout < 0) { - test.done(new Error("Timeout")); - } - setTimeout(waitForThread, 100); - } - - waitForThread(); - } -}); diff --git a/test/dynamicProxy.test.ts b/test/dynamicProxy.test.ts new file mode 100644 index 00000000..e82ba58b --- /dev/null +++ b/test/dynamicProxy.test.ts @@ -0,0 +1,276 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { Java, JavaObject } from "../java"; +import { expectJavaError, getJava } from "../testHelpers"; + +describe("Dynamic Proxy", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("0 Arguments", () => { + let callCount = 0; + + const myProxy = java.newProxy("RunInterface$Interface0Arg", { + run: function () { + callCount++; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + runInterface.run0ArgsSync(myProxy); + + expect(callCount).toBe(2); + }); + + test("1 Arguments", () => { + let runData = ""; + + const myProxy = java.newProxy("RunInterface$Interface1Arg", { + run: (str: string) => { + runData += str; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + runInterface.run1ArgsSync(myProxy); + + expect(runData).toBe("test1test1"); + }); + + test("1 Arguments with return data", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (i: number) => { + return i + 1; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runWithReturnSync(myProxy); + + expect(result).toBe(43); + }); + + test("Listener test", () => { + let runData = ""; + + const myProxy = java.newProxy("ListenerInterface", { + onEvent: (_list: JavaObject, _runtime: JavaObject) => { + runData = "onEvent"; + }, + }); + + const listenerTester = java.newInstanceSync("ListenerTester"); + listenerTester.setListenerSync(myProxy); + listenerTester.raiseEventSync(); + + expect(runData).toBe("onEvent"); + }); + + test("thread", async () => { + await new Promise((resolve, reject) => { + let callCount = 0; + + const myProxy = java.newProxy("java.lang.Runnable", { + run: function () { + callCount++; + }, + }); + + const thread = java.newInstanceSync("java.lang.Thread", myProxy); + thread.startSync(); + + let timeout = 50; + + function waitForThread(): void { + if (callCount === 1) { + return resolve(); + } + timeout--; + if (timeout < 0) { + return reject(new Error("Timeout")); + } + setTimeout(waitForThread, 100); + } + + waitForThread(); + }); + }); + + test("thread issue #143", async () => { + await new Promise((resolve) => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (i: number) => { + return i - 1; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + runInterface.runInAnotherThread(myProxy, (err: Error | undefined, result: number) => { + expect(err).toBeUndefined(); + expect(result).toBe(45); + resolve(); + }); + }); + }); + + test("java equals()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", {}); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runEqualsSync(myProxy); + + expect(result).toBe(false); + }); + + test("java equals() same instance", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", {}); + + const runInterface = java.newInstanceSync("RunInterface"); + runInterface.setInstanceSync(myProxy); + const result = runInterface.runEqualsInstanceSync(myProxy); + + expect(result).toBe(true); + }); + + test("java equals() different instance", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", {}); + const myProxy2 = java.newProxy("RunInterface$InterfaceWithReturn", {}); + + const runInterface = java.newInstanceSync("RunInterface"); + runInterface.setInstanceSync(myProxy); + const result = runInterface.runEqualsInstanceSync(myProxy2); + + expect(result).toBe(false); + }); + + test("js equals()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + equals: (_obj: JavaObject) => { + return true; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runEqualsSync(myProxy); + + expect(result).toBe(true); + }); + + test("java hashCode()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", {}); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runHashCodeSync(myProxy); + const result2 = runInterface.runHashCodeSync(myProxy); + const systemHashCode = java.callStaticMethodSync("java.lang.System", "identityHashCode", myProxy); + + expect(result).toBe(result2); + expect(result).toBe(systemHashCode); + }); + + test("js hashCode()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + hashCode: function () { + return 1234; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runHashCodeSync(myProxy); + + expect(result).toBe(1234); + }); + + test("java toString()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", {}); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runToStringSync(myProxy); + + expect(result).toBe("[object Object]"); + }); + + test("js toString()", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + toString: () => { + return "myRunInterface"; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + const result = runInterface.runToStringSync(myProxy); + + expect(result).toBe("myRunInterface"); + }); + + test("js string error", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (_i: JavaObject) => { + // eslint-disable-next-line @typescript-eslint/only-throw-error + throw "myError"; + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + try { + runInterface.runWithReturnSync(myProxy); + throw new Error("Exception was not thrown"); + } catch (e) { + expectJavaError(e); + expect(e.cause.getClassSync().getNameSync()).toBe("java.lang.RuntimeException"); + expect(e.message).toMatch(/Caused by: node\.NodeJsException:.*myError/); + } + }); + + test("js Error", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (_i: JavaObject) => { + throw new Error("newError"); + }, + }); + + const runInterface = java.newInstanceSync("RunInterface"); + try { + runInterface.runWithReturnSync(myProxy); + throw new Error("Exception was not thrown"); + } catch (e) { + expectJavaError(e); + expect(e.cause.getClassSync().getNameSync()).toBe("java.lang.RuntimeException"); + expect(e.message).toMatch(/Caused by: node\.NodeJsException:.*newError/); + } + }); + + test("invocationHandler", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (i: number) => { + return i + 2; + }, + }); + + const result = myProxy.invocationHandler.run(42); + + expect(result).toBe(44); + }); + + test("unref", () => { + const myProxy = java.newProxy("RunInterface$InterfaceWithReturn", { + run: (i: number) => { + return i + 1; + }, + }); + + myProxy.unref(); + + try { + myProxy.invocationHandler.run(42); + } catch (e) { + expectJavaError(e); + expect(e.message).toBe("dynamicProxyData has been destroyed or corrupted"); + } + + // call again + myProxy.unref(); + }); +}); diff --git a/test/importClass-test.js b/test/importClass-test.js deleted file mode 100644 index 195e4dd9..00000000 --- a/test/importClass-test.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var java = require("../testHelpers").java; -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Import Class'] = nodeunit.testCase({ - tearDown: function (callback) { - java.setStaticFieldValue("Test", "staticFieldInt", 42); - callback(); - }, - - "import": function (test) { - var Test = java.import('Test'); - test.equals(42, Test.staticFieldInt); - Test.staticFieldInt = 200; - test.equals(200, Test.staticFieldInt); - - test.equals(100, Test.staticMethodSync(99)); - Test.staticMethod(99, function (err, result) { - test.ok(!err); - test.equals(100, result); - - var testObj = new Test(5); - test.equals(5, testObj.getIntSync()); - test.done(); - }); - } -}); diff --git a/test/importClass.test.ts b/test/importClass.test.ts new file mode 100644 index 00000000..5086eb85 --- /dev/null +++ b/test/importClass.test.ts @@ -0,0 +1,64 @@ +import { afterEach, beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("Import Class", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + afterEach(() => { + java.setStaticFieldValue("Test", "staticFieldInt", 42); + }); + + test("import", async () => { + const Test = java.import("Test"); + expect(Test.staticFieldInt).toBe(42); + Test.staticFieldInt = 200; + expect(Test.staticFieldInt).toBe(200); + + expect(Test.staticMethodSync(99)).toBe(100); + await new Promise((resolve) => { + Test.staticMethod(99, (err: Error | undefined, result: number | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe(100); + + const testObj = new Test(5); + expect(testObj.getIntSync()).toBe(5); + resolve(); + }); + }); + }); + + test("import TestEnum with unsable name", () => { + const TestEnum = java.import("Test$Enum"); + + // 'foo' and 'bar' are valid enum names + expect(TestEnum.foo.toStringSync()).toBe("foo"); + expect(TestEnum.bar.toStringSync()).toBe("bar"); + + ["name", "arguments", "caller"].forEach(function (prop) { + expect(() => { + // The enum also defines 'name', 'caller', and 'attributes', but Javascript prevents us from using them, + // since these are unwritable properties of Function. + TestEnum[prop].toStringSync(); + }).toThrow(TypeError); + }); + }); + + test("import TestEnum and use alternate name", () => { + const TestEnum = java.import("Test$Enum"); + + // 'foo' and 'bar' are valid enum names + expect(TestEnum.foo.toStringSync()).toBe("foo"); + expect(TestEnum.bar.toStringSync()).toBe("bar"); + + // 'name', 'caller', and 'arguments' are not, so we must use e.g. 'name_' to reference the enum. + // But note that the value is still e.g. "name". + expect(TestEnum.name_.toStringSync()).toBe("name"); + expect(TestEnum.arguments_.toStringSync()).toBe("arguments"); + expect(TestEnum.caller_.toStringSync()).toBe("caller"); + }); +}); diff --git a/test/instanceof.test.ts b/test/instanceof.test.ts new file mode 100644 index 00000000..679a3d3d --- /dev/null +++ b/test/instanceof.test.ts @@ -0,0 +1,31 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("instanceOf", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("working", () => { + const subclass = java.newInstanceSync("Test$SubClass"); + if (!java.instanceOf(subclass, "Test$SuperClass")) { + throw new Error(subclass.getNameSync() + " should be an instance of Test$SuperClass"); + } + }); + + test("non-java object", () => { + if (java.instanceOf({}, "Test$SuperClass")) { + throw new Error("javascript objects are not instances of anything"); + } + }); + + test("bad type", () => { + const subclass = java.newInstanceSync("Test$SubClass"); + expect(() => { + java.instanceOf(subclass, "BadClassName"); + }).toThrow(); + }); +}); diff --git a/test/java-ambiguousMethod.test.ts b/test/java-ambiguousMethod.test.ts new file mode 100644 index 00000000..88237732 --- /dev/null +++ b/test/java-ambiguousMethod.test.ts @@ -0,0 +1,120 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("Java - Call Ambiguous Method", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("staticMethodAmbiguous (sync) - int passed to double", () => { + const result = java.callStaticMethodSync("Test", "staticMethodAmbiguous(Ljava/lang/Double;)I", 1); + expect(result).toBe(1); + }); + + test("staticMethodAmbiguous (sync) - double passed to int", () => { + const result = java.callStaticMethodSync("Test", "staticMethodAmbiguous(Ljava/lang/Integer;)I", 1.1); + expect(result).toBe(2); + }); + + test("staticMethodAmbiguous (sync) - method not found wrong argument type", () => { + expect(() => { + java.callStaticMethodSync("Test", "staticMethodAmbiguous(Ljava/lang/String;)I", 1); + }).toThrow(); + }); + + test("staticMethodAmbiguous (sync) - method failed because argument count mismatch", () => { + expect(() => { + java.callStaticMethodSync("Test", "staticMethodAmbiguous(Ljava/lang/Integer;)I", 1, 2); + }).toThrow(); + }); + + test("staticMethodAmbiguous - int passed to double", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodAmbiguous(Ljava/lang/Double;)I", + 1, + (err: Error | undefined, result: number | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe(1); + resolve(); + } + ); + }); + }); + + test("staticMethodAmbiguous - double passed to int", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodAmbiguous(Ljava/lang/Integer;)I", + 1.1, + (err: Error | undefined, result: number | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe(2); + resolve(); + } + ); + }); + }); + + test("staticMethodAmbiguous - method not found", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodAmbiguous(Ljava/lang/String;)I", + 1, + (err: Error | undefined, result: number | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + } + ); + }); + }); + + test("staticMethodAmbiguous - method argument count mismatch", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodAmbiguous(Ljava/lang/Integer;)I", + 1, + 2, + (err: Error | undefined, result: number | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + } + ); + }); + }); + + test("methodAmbiguous (sync) - int passed to double", () => { + const myTest = java.newInstanceSync("Test"); + const result = java.callMethodSync(myTest, "methodAmbiguous(Ljava/lang/Double;)I", 1); + expect(result).toBe(1); + }); + + test("methodAmbiguous (sync) - double passed to int", () => { + const myTest = java.newInstanceSync("Test"); + const result = java.callMethodSync(myTest, "methodAmbiguous(Ljava/lang/Integer;)I", 1.1); + expect(result).toBe(2); + }); + + test("methodAmbiguous (sync) - method not found wrong argument type", () => { + const myTest = java.newInstanceSync("Test"); + expect(() => { + java.callMethodSync(myTest, "methodAmbiguous(Ljava/lang/String;)I", 1); + }).toThrow(); + }); + + test("methodAmbiguous (sync) - method failed because argument count mismatch", () => { + const myTest = java.newInstanceSync("Test"); + expect(() => { + java.callMethodSync(myTest, "methodAmbiguous(Ljava/lang/Integer;)I", 1, 2); + }).toThrow(); + }); +}); diff --git a/test/java-callStaticMethod-test.js b/test/java-callStaticMethod-test.js deleted file mode 100644 index 05b20904..00000000 --- a/test/java-callStaticMethod-test.js +++ /dev/null @@ -1,119 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Java - Call Static Method'] = nodeunit.testCase({ - "callStaticMethod": function(test) { - java.callStaticMethod("Test", "staticMethod", function(err, result) { - test.ok(result); - test.equal(result, "staticMethod called"); - test.done(); - }); - }, - - "callStaticMethodSync": function(test) { - var result = java.callStaticMethodSync("Test", "staticMethod"); - test.ok(result); - test.equal(result, "staticMethod called"); - test.done(); - }, - - "callStaticMethod with args": function(test) { - java.callStaticMethod("Test", "staticMethod", 42, function(err, result) { - test.ok(result); - test.equal(result, 43); - test.done(); - }); - }, - - "callStaticMethodSync with args": function(test) { - var result = java.callStaticMethodSync("Test", "staticMethod", 42); - test.ok(result); - test.equal(result, 43); - test.done(); - }, - - "callStaticMethod bad class name": function(test) { - java.callStaticMethod("BadClassName", "staticMethod", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - - "callStaticMethodSync bad class name": function(test) { - test.throws(function() { - java.callStaticMethodSync("BadClassName", "staticMethod"); - }); - test.done(); - }, - - "callStaticMethod bad arg types": function(test) { - java.callStaticMethod("Test", "staticMethod", "z", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "callStaticMethodSync bad arg types": function(test) { - test.throws(function() { - java.callStaticMethodSync("Test", "staticMethod", "z"); - }); - test.done(); - }, - - "callStaticMethod bad number of args": function(test) { - java.callStaticMethod("Test", "staticMethod", 42, "z", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "callStaticMethodSync bad number of args": function(test) { - test.throws(function() { - java.callStaticMethodSync("Test", "staticMethod", 42, "z"); - }); - test.done(); - }, - - "callStaticMethod bad method name": function(test) { - java.callStaticMethod("Test", "badMethodName", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "callStaticMethodSync bad method name": function(test) { - test.throws(function() { - java.callStaticMethodSync("Test", "badMethodName"); - }); - test.done(); - }, - - "callStaticMethod exception thrown from method": function(test) { - var ex = java.newInstanceSync("java.lang.Exception", "my exception"); - java.callStaticMethod("Test", "staticMethodThrows", ex, function(err, result) { - test.ok(err); - test.ok(err.toString().match(/my exception/)); - test.ok(!result); - test.done(); - }); - }, - - "callStaticMethodSync exception thrown from method": function(test) { - var ex = java.newInstanceSync("java.lang.Exception", "my exception"); - try { - java.callStaticMethodSync("Test", "staticMethodThrows", ex); - test.fail("should throw"); - } catch(err) { - test.ok(err.toString().match(/my exception/)); - } - test.done(); - }, -}); diff --git a/test/java-callStaticMethod.test.ts b/test/java-callStaticMethod.test.ts new file mode 100644 index 00000000..1ad3eb4b --- /dev/null +++ b/test/java-callStaticMethod.test.ts @@ -0,0 +1,329 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { expectJavaError, getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("Java - Call Static Method", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("callStaticMethod", async () => { + await new Promise((resolve) => { + java.callStaticMethod("Test", "staticMethod", (err: Error | undefined, result: string | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result).toBe("staticMethod called"); + resolve(); + }); + }); + }); + + test("callStaticMethod without a callback", () => { + const result = java.callStaticMethod("Test", "staticMethod"); + expect(result).toBe( + `"Static method 'staticMethod' called without a callback did you mean to use the Sync version?"` + ); + }); + + test("callStaticMethodSync", () => { + const result = java.callStaticMethodSync("Test", "staticMethod"); + expect(result).toBeTruthy(); + expect(result).toBe("staticMethod called"); + }); + + test("callStaticMethod with args", async () => { + await new Promise((resolve) => { + java.callStaticMethod("Test", "staticMethod", 42, (err: Error | undefined, result: string | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result).toBe(43); + resolve(); + }); + }); + }); + + test("callStaticMethodSync with args", () => { + const result = java.callStaticMethodSync("Test", "staticMethod", 42); + expect(result).toBeTruthy(); + expect(result).toBe(43); + }); + + test("callStaticMethodSync with BigDecimal", () => { + const bigDecimal = java.newInstanceSync("java.math.BigDecimal", 100.1); + const result = java.callStaticMethodSync("Test", "staticBigDecimalToString", bigDecimal); + expect(result).toBeTruthy(); + expect(Math.abs(parseFloat(result) - 100.1) < 0.0001).toBeTruthy(); + }); + + test("callStaticMethod bad class name", async () => { + await new Promise((resolve) => { + java.callStaticMethod("BadClassName", "staticMethod", (err: Error | undefined, result: string | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("callStaticMethodSync bad class name", () => { + expect(() => { + java.callStaticMethodSync("BadClassName", "staticMethod"); + }).toThrow(); + }); + + test("callStaticMethod bad arg types", async () => { + await new Promise((resolve) => { + java.callStaticMethod("Test", "staticMethod", "z", (err: Error | undefined, result: string | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("callStaticMethodSync bad arg types", () => { + expect(() => { + java.callStaticMethodSync("Test", "staticMethod", "z"); + }).toThrow(); + }); + + test("callStaticMethod bad number of args", async () => { + await new Promise((resolve) => { + java.callStaticMethod("Test", "staticMethod", 42, "z", (err: Error | undefined, result: string | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("callStaticMethodSync bad number of args", () => { + expect(() => { + java.callStaticMethodSync("Test", "staticMethod", 42, "z"); + }).toThrow(); + }); + + test("callStaticMethod bad method name", async () => { + await new Promise((resolve) => { + java.callStaticMethod("Test", "badMethodName", (err: Error | undefined, result: string | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("callStaticMethodSync bad method name", () => { + expect(() => { + java.callStaticMethodSync("Test", "badMethodName"); + }).toThrow(); + }); + + test("callStaticMethod exception thrown from method (sync)", () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + let result; + try { + result = java.callStaticMethodSync("Test", "staticMethodThrows", ex); + throw new Error("expected error"); + } catch (err) { + expectJavaError(err); + expect(err.cause?.getMessageSync()).toBe("my exception"); + expect(err.toString()).toMatch(/my exception/); + expect(result).toBeFalsy(); + } + }); + + test("staticMethodThrows exception thrown from method (sync)", () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + try { + java.callStaticMethodSync("Test", "staticMethodThrows", ex); + throw new Error("should throw"); + } catch (err) { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + } + }); + + test("staticMethodThrows exception thrown from method", async () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + await new Promise((resolve) => { + java.callStaticMethod("Test", "staticMethodThrows", ex, (err: Error | undefined, result: string | undefined) => { + expectJavaError(err); + expect(err.cause.getMessageSync()).toBe("my exception"); + expect(err.toString()).toMatch(/my exception/); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("staticMethodThrowsNewException exception thrown from method (sync)", () => { + try { + java.callStaticMethodSync("Test", "staticMethodThrowsNewException"); + throw new Error("should throw"); + } catch (err) { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + } + }); + + test("staticMethodThrowsNewException exception thrown from method", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodThrowsNewException", + (err: Error | undefined, result: string | undefined) => { + expectJavaError(err); + expect(err.cause.getMessageSync()).toBe("my exception"); + expect(err.toString()).toMatch(/my exception/); + expect(result).toBeFalsy(); + resolve(); + } + ); + }); + }); + + test("methodThrows exception thrown from method (sync)", () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + const myTest = java.newInstanceSync("Test"); + try { + myTest.methodThrowsSync(ex); + throw new Error("should throw"); + } catch (err) { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + } + }); + + test("methodThrows exception thrown from method", async () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + const myTest = java.newInstanceSync("Test"); + await new Promise((resolve) => { + return myTest.methodThrows(ex, (err: Error | undefined) => { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + resolve(); + }); + }); + }); + + test("methodThrowsNewException exception thrown from method (sync)", () => { + const myTest = java.newInstanceSync("Test"); + try { + myTest.methodThrowsNewExceptionSync(); + throw new Error("should throw"); + } catch (err) { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + } + }); + + test("methodThrowsNewException exception thrown from method", async () => { + const myTest = java.newInstanceSync("Test"); + await new Promise((resolve) => { + myTest.methodThrowsNewException((err: Error | undefined) => { + expectJavaError(err); + expect(err.toString()).toMatch(/my exception/); + resolve(); + }); + }); + }); + + test("char array", async () => { + const charArray = java.newArray("char", "hello world\n".split("")); + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodCharArrayToString", + charArray, + (err: Error | undefined, result: string | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result).toBe("hello world\n"); + resolve(); + } + ); + }); + }); + + test("String passed in for Object", async () => { + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "static2Objects", + "1", + "2", + (err: Error | undefined, result: boolean | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe(false); + resolve(); + } + ); + }); + }); + + test("java.lang.Long addition", () => { + const javaLong = java.newInstanceSync("java.lang.Long", 5); + expect(javaLong.toString()).toBe("5"); + const result = javaLong + 1; + expect(result).toBe(6); + }); + + test("java.lang.Long calls (java Long)", async () => { + const javaLong = java.newInstanceSync("java.lang.Long", 5); + await new Promise((resolve) => { + java.callStaticMethod( + "Test", + "staticMethodLongToString", + javaLong, + (err: Error | undefined, result: string | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result).toBe("5"); + resolve(); + } + ); + }); + }); + + test("Call method that returns a long", () => { + java.callStaticMethod( + "Test", + "staticMethodReturnLong", + (err: Error | undefined, result: { longValue: string } | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result?.longValue).toBe("9223372036854775807"); + } + ); + }); + + test("Call method with nested enum value", () => { + const Test = java.import("Test"); + Test.staticEnumToStringSync(Test.StaticEnum.Value1); + const str = Test.staticEnumToStringSync(Test.StaticEnum.Value1); // call it twice to ensure memo-ize is working + expect(str).toBe("Value1"); + }); + + test("Call static method with varargs", () => { + const Test = java.import("Test"); + + let str = Test.staticVarargsSync(5, java.newArray("java.lang.String", ["a", "b", "c"])); + expect(str).toBe("5abc"); + + str = Test.staticVarargsSync(5, "a", "b", "c"); + expect(str).toBe("5abc"); + }); + + test("Call static method named name_", async () => { + await new Promise((resolve) => { + const Test = java.import("Test"); + Test.name_((err: Error | undefined) => { + expect(err).toBeFalsy(); + resolve(); + }); + }); + }); +}); diff --git a/test/java-newInstance-test.js b/test/java-newInstance-test.js deleted file mode 100644 index c6b1af31..00000000 --- a/test/java-newInstance-test.js +++ /dev/null @@ -1,108 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Java - New Instance'] = nodeunit.testCase({ - "newInstance": function(test) { - java.newInstance("Test", function(err, result) { - test.ok(result); - test.equal(result.getClassSync().toStringSync(), "class Test"); - test.ok(result.getInt); - test.ok(result.getIntSync); - test.ok(!result.staticMethod); - test.ok(!result.staticMethodSync); - test.equal(result.nonstaticInt, 42); - test.done(); - }); - }, - - "newInstanceSync": function(test) { - var result = java.newInstanceSync("Test"); - test.ok(result); - test.equal(result.getClassSync().toStringSync(), "class Test"); - test.done(); - }, - - "newInstance with args": function(test) { - java.newInstance("Test", 42, function(err, result) { - test.ok(result); - test.equal(result.getIntSync(), 42); - test.done(); - }); - }, - - "newInstanceSync with args": function(test) { - var result = java.newInstanceSync("Test", 42); - test.ok(result); - test.equal(result.getIntSync(), 42); - test.done(); - }, - - "newInstance bad class name": function(test) { - java.newInstance("BadClassName", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "newInstanceSync bad class name": function(test) { - test.throws(function() { - java.newInstanceSync("BadClassName"); - }); - test.done(); - }, - - "newInstance bad arg types": function(test) { - java.newInstance("Test", "z", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "newInstanceSync bad arg types": function(test) { - test.throws(function() { - java.newInstanceSync("Test", "z"); - }); - test.done(); - }, - - "newInstance bad number of args": function(test) { - java.newInstance("Test", 42, "z", function(err, result) { - test.ok(err); - test.ok(!result); - test.done(); - }); - }, - - "newInstanceSync bad number of args": function(test) { - test.throws(function() { - java.newInstanceSync("Test", 42, "z"); - }); - test.done(); - }, - - "newInstance exception thrown from constructor": function(test) { - var ex = java.newInstanceSync("java.lang.Exception", "my exception"); - java.newInstance("TestExceptions", ex, function(err, result) { - test.ok(err); - test.ok(err.toString().match(/my exception/)); - test.ok(!result); - test.done(); - }); - }, - - "newInstanceSync exception thrown from constructor": function(test) { - var ex = java.newInstanceSync("java.lang.Exception", "my exception"); - try { - java.newInstanceSync("TestExceptions", ex); - test.fail("should throw"); - } catch(err) { - test.ok(err.toString().match(/my exception/)); - } - test.done(); - }, -}); diff --git a/test/java-newInstance.test.ts b/test/java-newInstance.test.ts new file mode 100644 index 00000000..f0ccece4 --- /dev/null +++ b/test/java-newInstance.test.ts @@ -0,0 +1,126 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { Java, JavaObject } from "../java"; +import { getJava } from "../testHelpers"; + +describe("Java - New Instance", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("newInstance", async () => { + await new Promise((resolve) => { + java.newInstance("Test", (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result.getClassSync().toStringSync()).toBe("class Test"); + expect(result.getInt).toBeTruthy(); + expect(result.getIntSync).toBeTruthy(); + expect(result.staticMethod).toBeFalsy(); + expect(result.staticMethodSync).toBeFalsy(); + expect(result.nonstaticInt).toBe(42); + resolve(); + }); + }); + }); + + test("newInstanceSync", () => { + const result = java.newInstanceSync("Test"); + expect(result).toBeTruthy(); + expect(result.getClassSync().toStringSync()).toBe("class Test"); + }); + + test("newInstance with args", async () => { + await new Promise((resolve) => { + java.newInstance("Test", 42, (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBeTruthy(); + expect(result.getIntSync()).toBe(42); + resolve(); + }); + }); + }); + + test("newInstanceSync with args", () => { + const result = java.newInstanceSync("Test", 42); + expect(result).toBeTruthy(); + expect(result.getIntSync()).toBe(42); + }); + + test("newInstance bad class name", async () => { + await new Promise((resolve) => { + java.newInstance("BadClassName", (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("newInstanceSync bad class name", () => { + expect(() => { + java.newInstanceSync("BadClassName"); + }).toThrow(); + }); + + test("newInstance bad arg types", async () => { + await new Promise((resolve) => { + java.newInstance("Test", "a", (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("newInstanceSync bad arg types", () => { + expect(() => { + java.newInstanceSync("Test", "a"); + }).toThrow(); + }); + + test("newInstance bad number of args", async () => { + await new Promise((resolve) => { + java.newInstance("Test", 42, 15, (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeTruthy(); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("newInstanceSync bad number of args", () => { + expect(() => { + java.newInstanceSync("Test", 42, 15); + }).toThrow(); + }); + + test("newInstance exception thrown from constructor", async () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + await new Promise((resolve) => { + java.newInstance("TestExceptions", ex, (err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeTruthy(); + expect(err?.toString()).toMatch(/my exception/); + expect(result).toBeFalsy(); + resolve(); + }); + }); + }); + + test("newInstanceSync exception thrown from constructor", () => { + const ex = java.newInstanceSync("java.lang.Exception", "my exception"); + expect(() => java.newInstanceSync("TestExceptions", ex)).toThrowError(/my exception/); + }); + + test("newInstanceSync with varargs", () => { + let result = java.newInstanceSync("Test", 42, java.newArray("java.lang.String", ["a", "b"])); + expect(result).toBeTruthy(); + + result = java.newInstanceSync("Test", 42, "a"); + expect(result).toBeTruthy(); + + result = java.newInstanceSync("Test", 42, "a", "b", "c"); + expect(result).toBeTruthy(); + }); +}); diff --git a/test/java-staticField-test.js b/test/java-staticField-test.js deleted file mode 100644 index d3f4fad0..00000000 --- a/test/java-staticField-test.js +++ /dev/null @@ -1,45 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Java - Static Field'] = nodeunit.testCase({ - tearDown: function (callback) { - java.setStaticFieldValue("Test", "staticFieldInt", 42); - callback(); - }, - - "getStaticFieldValue int": function(test) { - var val = java.getStaticFieldValue("Test", "staticFieldInt"); - test.equal(val, 42); - test.done(); - }, - - "setStaticFieldValue int": function(test) { - java.setStaticFieldValue("Test", "staticFieldInt", 112); - var val = java.getStaticFieldValue("Test", "staticFieldInt"); - test.equal(val, 112); - test.done(); - }, - - "getStaticFieldValue double": function(test) { - var val = java.getStaticFieldValue("Test", "staticFieldDouble"); - test.equal(val, 42.5); - test.done(); - }, - - "setStaticFieldValue double": function(test) { - java.setStaticFieldValue("Test", "staticFieldDouble", 112.12); - var val = java.getStaticFieldValue("Test", "staticFieldDouble"); - test.equal(val, 112.12); - test.done(); - }, - - "setStaticFieldValue double (set int)": function(test) { - java.setStaticFieldValue("Test", "staticFieldDouble", 112); - var val = java.getStaticFieldValue("Test", "staticFieldDouble"); - test.equal(val, 112); - test.done(); - }, -}); diff --git a/test/java-staticField.test.ts b/test/java-staticField.test.ts new file mode 100644 index 00000000..cda57ab1 --- /dev/null +++ b/test/java-staticField.test.ts @@ -0,0 +1,43 @@ +import { afterEach, beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("Java - Static Field", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + afterEach(() => { + java.setStaticFieldValue("Test", "staticFieldInt", 42); + }); + + test("getStaticFieldValue int", () => { + const val = java.getStaticFieldValue("Test", "staticFieldInt"); + expect(val).toBe(42); + }); + + test("setStaticFieldValue int", () => { + java.setStaticFieldValue("Test", "staticFieldInt", 112); + const val = java.getStaticFieldValue("Test", "staticFieldInt"); + expect(val).toBe(112); + }); + + test("getStaticFieldValue double", () => { + const val = java.getStaticFieldValue("Test", "staticFieldDouble"); + expect(val).toBe(42.5); + }); + + test("setStaticFieldValue double", () => { + java.setStaticFieldValue("Test", "staticFieldDouble", 112.12); + const val = java.getStaticFieldValue("Test", "staticFieldDouble"); + expect(val).toBe(112.12); + }); + + test("setStaticFieldValue double (set int)", () => { + java.setStaticFieldValue("Test", "staticFieldDouble", 112); + const val = java.getStaticFieldValue("Test", "staticFieldDouble"); + expect(val).toBe(112); + }); +}); diff --git a/test/javaObject-test.js b/test/javaObject-test.js deleted file mode 100644 index c99ec95f..00000000 --- a/test/javaObject-test.js +++ /dev/null @@ -1,19 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Java Object'] = nodeunit.testCase({ - setUp: function(callback) { - this.testObj = java.newInstanceSync("Test"); - callback(); - }, - - "field": function(test) { - test.equal(this.testObj.nonstaticInt, 42); - this.testObj.nonstaticInt = 112; - test.equal(this.testObj.nonstaticInt, 112); - test.done(); - } -}); diff --git a/test/javaObject.test.ts b/test/javaObject.test.ts new file mode 100644 index 00000000..c3569f22 --- /dev/null +++ b/test/javaObject.test.ts @@ -0,0 +1,19 @@ +import { beforeEach, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java, JavaObject } from "../java"; + +describe("Java Object", () => { + let testObj: JavaObject; + let java!: Java; + + beforeEach(async () => { + java = await getJava(); + testObj = java.newInstanceSync("Test"); + }); + + test("field", () => { + expect(testObj.nonstaticInt).toBe(42); + testObj.nonstaticInt = 112; + expect(testObj.nonstaticInt).toBe(112); + }); +}); diff --git a/test/promises.test.ts b/test/promises.test.ts new file mode 100644 index 00000000..2f757630 --- /dev/null +++ b/test/promises.test.ts @@ -0,0 +1,81 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("Promises", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("create an instance of a class and call methods (getClassPromise & getNamePromise)", async () => { + // Adapted from a test in simple-test.js + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const list = await (java as any).newInstancePromise("java.util.ArrayList"); + expect(list).toBeTruthy(); + + const clazz = await list.getClassPromise(); + expect(clazz).toBeTruthy(); + + const name = await clazz.getNamePromise(); + expect(name).toBe("java.util.ArrayList"); + }); + + test("import and execute promisified static method", async () => { + const Test = java.import("Test"); + const result = await Test.staticMethodPromise(99); + expect(result).toBe(100); + }); + + test("run promisified method of Java module (newInstancePromise)", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const list = await (java as any).newInstancePromise("java.util.ArrayList"); + expect(list).toBeTruthy(); + + const clazz = await list.getClassPromise(); + expect(clazz).toBeTruthy(); + + const name = await clazz.getNamePromise(); + expect(name).toBe("java.util.ArrayList"); + }); + + test("run chained promisified methods (of class java.util.ArrayList)", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const list = await (java as any).newInstancePromise("java.util.ArrayList"); + expect(list).toBeTruthy(); + + const clazz = await list.getClassPromise(); + expect(clazz).toBeTruthy(); + + const name = await clazz.getNamePromise(); + expect(name).toBe("java.util.ArrayList"); + + await list.addPromise("hello"); + await list.addPromise("world"); + await list.addPromise("boo"); + const it = await list.iteratorPromise(); + expect(it).toBeTruthy(); + + let val = await it.nextPromise(); + expect(val).toBeTruthy(); + console.log(typeof val, val); + expect(val).toBe("hello"); // java.lang.InternalError exception thrown here with OpenJDK + + val = await it.nextPromise(); + expect(val).toBeTruthy(); + console.log(typeof val, val); + expect(val).toBe("world"); + + val = await it.nextPromise(); + expect(val).toBeTruthy(); + console.log(typeof val, val); + expect(val).toBe("boo"); + + const more = await it.hasNextPromise(); + console.log(typeof more, more); + expect(more).toBeFalsy(); + + await expect(async () => await it.nextPromise()).rejects.toThrowError(); + }); +}); diff --git a/test/simple-test.js b/test/simple-test.js deleted file mode 100644 index 59a649f3..00000000 --- a/test/simple-test.js +++ /dev/null @@ -1,111 +0,0 @@ -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Simple'] = nodeunit.testCase({ - "test classpath commons lang": function(test) { - var result = java.callStaticMethodSync("org.apache.commons.lang3.ObjectUtils", "toString", "test"); - console.log("org.apache.commons.lang3.ObjectUtils.toString:", result); - test.equal(result, "test"); - test.done(); - }, - - "test static calls": function(test) { - var result = java.callStaticMethodSync("java.lang.System", "currentTimeMillis"); - console.log("currentTimeMillis:", result); - test.ok(result); - test.done(); - }, - - "test static calls single argument": function(test) { - var result = java.callStaticMethodSync("java.lang.System", "getProperty", "os.version"); - console.log("os.version:", result); - test.ok(result); - test.done(); - }, - - "test method does not exists (sync)": function(test) { - test.throws( - function() { - java.callStaticMethodSync("java.lang.System", "badMethod"); - } - ); - test.done(); - }, - - "test method does not exists (async)": function(test) { - java.callStaticMethod("java.lang.System", "badMethod", function(err, result) { - if(err) { test.done(); return; } - test.done(new Error("should throw exception")); - }); - }, - - "create an instance of a class and call methods (getName) (async)": function(test) { - java.newInstance("java.util.ArrayList", function(err, list) { - if(err) { console.log(err); return; } - test.ok(list); - if(list) { - list.getClass(function(err, result) { - if(err) { console.log(err); return; } - result.getName(function(err, result) { - if(err) { console.log(err); return; } - test.equal(result, "java.util.ArrayList"); - test.done(); - }); - }); - } - }); - }, - - "create an instance of a class and call methods (getName) (sync)": function(test) { - var list = java.newInstanceSync("java.util.ArrayList"); - test.equal(list.sizeSync(), 0); - list.addSync("hello"); - list.addSync("world"); - test.equal(list.sizeSync(), 2); - var item0 = list.getSync(0); - test.equal(item0, "hello"); - var clazz = list.getClassSync(); - var result = clazz.getNameSync(); - test.equal(result, "java.util.ArrayList"); - test.done(); - }, - - "create an instance of a class and call methods (size) (async)": function(test) { - java.newInstance("java.util.ArrayList", function(err, list) { - if(err) { console.log(err); return; } - test.ok(list); - if(list) { - list.size(function(err, result) { - if(err) { console.log(err); return; } - test.equal(result, 0); - test.done(); - }); - } - }); - }, - - "passing objects to methods": function(test) { - var data = java.newArray("byte", toAsciiArray("hello world\n")); - //console.log("data", data.toStringSync()); - var stream = java.newInstanceSync("java.io.ByteArrayInputStream", data); - //console.log("stream", stream); - var reader = java.newInstanceSync("java.io.InputStreamReader", stream); - //console.log("reader", reader); - var bufferedReader = java.newInstanceSync("java.io.BufferedReader", reader); - var str = bufferedReader.readLineSync(); - console.log("bufferedReader.readLineSync", str); - test.equal(str, "hello world"); - test.done(); - } -}); - -function toAsciiArray(str) { - var results = []; - for(var i=0; i { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("test classpath commons lang", () => { + const result = java.callStaticMethodSync("org.apache.commons.lang3.ObjectUtils", "toString", "test"); + console.log("org.apache.commons.lang3.ObjectUtils.toString:", result); + expect(result).toBe("test"); + }); + + test("test adding to classpath after other calls are made", () => { + java.callStaticMethodSync("java.lang.System", "currentTimeMillis"); + expect(() => { + console.log("classpath", java.classpath); + java.classpath = ["test/"]; + }).toThrow(); + }); + + test("test changing options after other calls are made", () => { + java.callStaticMethodSync("java.lang.System", "currentTimeMillis"); + expect(() => { + console.log("options", java.options); + java.options = ["newoption"]; + }).toThrow(); + }); + + test("test changing nativeBindingLocation after other calls are made", () => { + java.callStaticMethodSync("java.lang.System", "currentTimeMillis"); + expect(() => { + console.log("nativeBindingLocation", java.nativeBindingLocation); + java.nativeBindingLocation = "newNativeBindingLocation"; + }).toThrow(); + }); + + test("test static calls", () => { + const result = java.callStaticMethodSync("java.lang.System", "currentTimeMillis"); + console.log("currentTimeMillis:", result); + expect(result).toBeTruthy(); + }); + + test("test static calls single argument", () => { + const result = java.callStaticMethodSync("java.lang.System", "getProperty", "os.version"); + console.log("os.version:", result); + expect(result).toBeTruthy(); + }); + + test("test method does not exists (sync)", () => { + expect(() => { + java.callStaticMethodSync("java.lang.System", "badMethod"); + }).toThrow(); + }); + + test("test method does not exists (async)", () => { + java.callStaticMethod("java.lang.System", "badMethod", (err: Error | undefined) => { + if (err) { + return; + } + new Error("should throw exception"); + }); + }); + + test("create an instance of a class and call methods (getName) (async)", async () => { + await new Promise((resolve) => { + java.newInstance("java.util.ArrayList", (err: Error | undefined, list: JavaObject | undefined) => { + expect(err).toBeFalsy(); + expect(list).toBeTruthy(); + list.getClass((err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeFalsy(); + result.getName((err: Error | undefined, result: JavaObject | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe("java.util.ArrayList"); + resolve(); + }); + }); + }); + }); + }); + + test("findClassSync", async () => { + const arrayListClass = java.findClassSync("java.util.ArrayList"); + expect(arrayListClass).toBeTruthy(); + expect(arrayListClass.getNameSync()).toBe("java.util.ArrayList"); + }); + + test("findClassSync not found", async () => { + expect(() => { + java.findClassSync("java.util.MissingClass"); + }).toThrowError(/Could not create class java.util.MissingClass/); + }); + + test("call method", async () => { + const list = java.newInstanceSync("java.util.ArrayList"); + await new Promise((resolve) => { + java.callMethod(list, "add", "test", (err: Error | undefined) => { + expect(err).toBeFalsy(); + expect(list.sizeSync()).toBe(1); + resolve(); + }); + }); + }); + + test("call method (sync)", async () => { + const list = java.newInstanceSync("java.util.ArrayList"); + java.callMethodSync(list, "add", ["test"]); + expect(list.sizeSync()).toBe(1); + }); + + test("create an instance of a class and call methods (getName) (sync)", () => { + const list = java.newInstanceSync("java.util.ArrayList"); + expect(list.sizeSync()).toBe(0); + list.addSync("hello"); + list.addSync("world"); + expect(list.sizeSync()).toBe(2); + const item0 = list.getSync(0); + expect(item0).toBe("hello"); + const clazz = list.getClassSync(); + const result = clazz.getNameSync(); + expect(result).toBe("java.util.ArrayList"); + }); + + test("create an instance of a class and call methods (size) (async)", async () => { + await new Promise((resolve) => { + java.newInstance("java.util.ArrayList", (err: Error | undefined, list: JavaObject | undefined) => { + expect(err).toBeFalsy(); + expect(list).toBeTruthy(); + list.size((err: Error | undefined, result: number | undefined) => { + expect(err).toBeFalsy(); + expect(result).toBe(0); + resolve(); + }); + }); + }); + }); + + test("passing objects to methods", () => { + const dataArray = "hello world\n".split("").map(function (c) { + return java.newByte(c.charCodeAt(0)); + }); + const data = java.newArray("byte", dataArray); + const stream = java.newInstanceSync("java.io.ByteArrayInputStream", data); + const reader = java.newInstanceSync("java.io.InputStreamReader", stream); + const bufferedReader = java.newInstanceSync("java.io.BufferedReader", reader); + const str = bufferedReader.readLineSync(); + expect(str).toBe("hello world"); + }); + + test("method returning an array of ints sync", () => { + const arr = java.callStaticMethodSync("Test", "getArrayOfInts"); + expect(arr.length).toBe(5); + expect(arr[0]).toBe(1); + expect(arr[1]).toBe(2); + expect(arr[2]).toBe(3); + expect(arr[3]).toBe(4); + expect(arr[4]).toBe(5); + }); + + test("method returning an array of bytes sync", () => { + const arr = java.callStaticMethodSync("Test", "getArrayOfBytes"); + expect(arr.length).toBe(5); + expect(arr[0]).toBe(1); + expect(arr[1]).toBe(2); + expect(arr[2]).toBe(3); + expect(arr[3]).toBe(4); + expect(arr[4]).toBe(5); + }); + + test("method returning an array of bools sync", () => { + const arr = java.callStaticMethodSync("Test", "getArrayOfBools"); + expect(arr.length).toBe(5); + expect(arr[0]).toBe(true); + expect(arr[1]).toBe(true); + expect(arr[2]).toBe(false); + expect(arr[3]).toBe(true); + expect(arr[4]).toBe(false); + }); + + test("method returning an array of doubles sync", () => { + const arr = java.callStaticMethodSync("Test", "getArrayOfDoubles"); + expect(arr.length).toBe(5); + expect(arr[0]).toBe(1); + expect(arr[1]).toBe(2); + expect(arr[2]).toBe(3); + expect(arr[3]).toBe(4); + expect(arr[4]).toBe(5); + }); + + test("method returning an array of floats sync", () => { + const arr = java.callStaticMethodSync("Test", "getArrayOfFloats"); + expect(arr.length).toBe(5); + expect(arr[0]).toBe(1); + expect(arr[1]).toBe(2); + expect(arr[2]).toBe(3); + expect(arr[3]).toBe(4); + expect(arr[4]).toBe(5); + }); + + test("method returning an array of longs sync", () => { + let arr = java.callStaticMethodSync("Test", "getArrayOfLongs"); + arr = arr.map((l: JavaObject) => { + return l.toStringSync(); + }); + expect(arr.length).toBe(5); + expect(arr[0]).toBe("9223372036854775807"); + expect(arr[1]).toBe("-9223372036854775808"); + expect(arr[2]).toBe("3"); + expect(arr[3]).toBe("4"); + expect(arr[4]).toBe("5"); + }); + + test("method returning a string (Unicode BMP)", () => { + const s = java.callStaticMethodSync("Test", "getUnicodeBMP"); + expect(s).toBe("\u2605"); + }); + + test("method returning a string (Unicode SMP)", () => { + const s = java.callStaticMethodSync("Test", "getUnicodeSMP"); + // The below string is U+1F596, represented as surrogate pairs + expect(s).toBe("\uD83D\uDD96"); + }); + + test("method returning a string (NULL char)", () => { + const s = java.callStaticMethodSync("Test", "getUnicodeNull"); + expect(s).toBe("\0"); + }); + + test("method taking a byte", () => { + const b = java.newByte(1); + expect(b.getClassSync().getNameSync()).toBe("java.lang.Byte"); + expect(b.toStringSync()).toBe("1"); + const r = java.callStaticMethodSync("Test", "staticByte", b); + expect(r).toBe(1); + }); + + test("method taking a short", () => { + const s = java.newShort(1); + expect(s.getClassSync().getNameSync()).toBe("java.lang.Short"); + expect(s.toStringSync()).toBe("1"); + const r = java.callStaticMethodSync("Test", "staticShort", s); + expect(r).toBe(1); + }); + + test("method taking a double", () => { + const s = java.newDouble(3.14); + expect(s.getClassSync().getNameSync()).toBe("java.lang.Double"); + expect(s.toStringSync()).toBe("3.14"); + const r = java.callStaticMethodSync("Test", "staticDouble", s); + expect(Math.abs(r - 3.14) < 0.0001, r + " != 3.14").toBeTruthy(); + }); + + test("method taking a float", () => { + const s = java.newFloat(3.14); + expect(s.getClassSync().getNameSync()).toBe("java.lang.Float"); + expect(s.toStringSync()).toBe("3.14"); + const r = java.callStaticMethodSync("Test", "staticFloat", s); + expect(Math.abs(r - 3.14) < 0.0001, r + " != 3.14").toBeTruthy(); + }); + + test("method taking a long", () => { + const l = java.newLong(1); + expect(l.getClassSync().getNameSync()).toBe("java.lang.Long"); + expect(l.toStringSync()).toBe("1"); + const r = java.callStaticMethodSync("Test", "staticLong", l); + expect(r).toBe(1); + }); + + test("method taking a char (number)", () => { + const ch = java.newChar(97); // 'a' + expect(ch.getClassSync().getNameSync()).toBe("java.lang.Character"); + expect(ch.toStringSync()).toBe("a"); + const r = java.callStaticMethodSync("Test", "staticChar", ch); + expect(r).toBe(97); + }); + + test("method taking a char (string)", () => { + const ch = java.newChar("a"); + expect(ch.getClassSync().getNameSync()).toBe("java.lang.Character"); + expect(ch.toStringSync()).toBe("a"); + const r = java.callStaticMethodSync("Test", "staticChar", ch); + expect(r).toBe(97); + }); + + test("method taking a string (Unicode BMP)", () => { + const s = "\u2605"; + const r = java.callStaticMethodSync("Test", "staticString", s); + expect(r).toBe(s); + }); + + test("method taking a string (Unicode SMP)", () => { + // The below string is U+1F596, represented as surrogate pairs + const s = "\uD83D\uDD96"; + const r = java.callStaticMethodSync("Test", "staticString", s); + expect(r).toBe(s); + }); + + test("method taking a string (with null char)", () => { + const s = "\0"; + const r = java.callStaticMethodSync("Test", "staticString", s); + expect(r).toBe(s); + }); + + test("new boolean array object", () => { + const booleanArray = java.newArray("java.lang.Boolean", [true, false]); + const r = java.callStaticMethodSync("Test", "staticBooleanArray", booleanArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(true); + expect(r[1]).toBe(false); + }); + + test("new byte array object", () => { + const byteArray = java.newArray("byte", [1, 2, 3]); + expect(byteArray.length).toBe(3); + expect(byteArray[0]).toBe(1); + expect(byteArray[1]).toBe(2); + expect(byteArray[2]).toBe(3); + }); + + test("new boolean array", () => { + const booleanArray = java.newArray("boolean", [true, false]); + const r = java.callStaticMethodSync("Test", "staticBooleanArray", booleanArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(true); + expect(r[1]).toBe(false); + }); + + test("new int array", () => { + const intArray = java.newArray("int", [1, 2]); + const r = java.callStaticMethodSync("Test", "staticIntArray", intArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(1); + expect(r[1]).toBe(2); + }); + + test("new double array", () => { + const doubleArray = java.newArray("double", [1.2, 4]); + const r = java.callStaticMethodSync("Test", "staticDoubleArray", doubleArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(1.2); + expect(r[1]).toBe(4); + }); + + test("new short array objects", () => { + const shortArray = java.newArray( + "java.lang.Short", + [1, 2].map(function (c) { + return java.newShort(c); + }) + ); + const r = java.callStaticMethodSync("Test", "staticShortArray", shortArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(1); + expect(r[1]).toBe(2); + }); + + test("new short array", () => { + const shortArray = java.newArray("short", [1, 2]); + const r = java.callStaticMethodSync("Test", "staticShortArray", shortArray); + expect(r.length).toBe(2); + expect(r[0]).toBe(1); + expect(r[1]).toBe(2); + }); +}); diff --git a/test/utils-types-test.js b/test/utils-types-test.js deleted file mode 100644 index 4ce33c6b..00000000 --- a/test/utils-types-test.js +++ /dev/null @@ -1,51 +0,0 @@ - -var java = require("../testHelpers").java; - -var nodeunit = require("nodeunit"); -var util = require("util"); - -exports['Utils - Types'] = nodeunit.testCase({ - "Array of Objects": function(test) { - var val = java.getStaticFieldValue("Test", "staticArrayObjects"); - test.equal(null, val); - - java.setStaticFieldValue("Test", "staticArrayObjects", java.newArray("Test", [ - java.newInstanceSync("Test", 1), - java.newInstanceSync("Test", 2), - java.newInstanceSync("Test", 3) - ])); - - val = java.getStaticFieldValue("Test", "staticArrayObjects"); - test.ok(val); - test.equal(val.length, 3); - test.equal(val[0].getIntSync(), 1); - test.equal(val[1].getIntSync(), 2); - test.equal(val[2].getIntSync(), 3); - test.done(); - }, - - "Static Method Overload": function(test) { - var result = java.callStaticMethodSync("Test", "staticMethodOverload", "a"); - test.equal(result, 1); - result = java.callStaticMethodSync("Test", "staticMethodOverload", 1); - test.equal(result, 2); - result = java.callStaticMethodSync("Test", "staticMethodOverload", java.newInstanceSync("Test$SuperClass")); - test.equal(result, 3); - result = java.callStaticMethodSync("Test", "staticMethodOverload", java.newInstanceSync("Test$SubClass")); - test.equal(result, 4); - test.done(); - }, - - "Method Overload": function(test) { - var testObj = java.newInstanceSync("Test"); - var result = testObj.methodOverloadSync("a"); - test.equal(result, 1); - result = testObj.methodOverloadSync(1); - test.equal(result, 2); - result = testObj.methodOverloadSync(java.newInstanceSync("Test$SuperClass")); - test.equal(result, 3); - result = testObj.methodOverloadSync(java.newInstanceSync("Test$SubClass")); - test.equal(result, 4); - test.done(); - } -}); diff --git a/test/utils-types.test.ts b/test/utils-types.test.ts new file mode 100644 index 00000000..0cb38d2d --- /dev/null +++ b/test/utils-types.test.ts @@ -0,0 +1,74 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("Utils - Types", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("Array of Objects", () => { + let val = java.getStaticFieldValue("Test", "staticArrayObjects"); + expect(val).toBeNull(); + + java.setStaticFieldValue( + "Test", + "staticArrayObjects", + java.newArray("Test", [ + java.newInstanceSync("Test", 1), + java.newInstanceSync("Test", 2), + java.newInstanceSync("Test", 3), + ]) + ); + + val = java.getStaticFieldValue("Test", "staticArrayObjects"); + expect(val).toBeTruthy(); + expect(val.length).toBe(3); + expect(val[0].getIntSync()).toBe(1); + expect(val[1].getIntSync()).toBe(2); + expect(val[2].getIntSync()).toBe(3); + }); + + test("Static Method Overload", () => { + let result = java.callStaticMethodSync("Test", "staticMethodOverload", "a"); + expect(result).toBe(1); + result = java.callStaticMethodSync("Test", "staticMethodOverload", 1); + expect(result).toBe(2); + result = java.callStaticMethodSync("Test", "staticMethodOverload", java.newInstanceSync("Test$SuperClass")); + expect(result).toBe(3); + result = java.callStaticMethodSync("Test", "staticMethodOverload", java.newInstanceSync("Test$SubClass")); + expect(result).toBe(4); + }); + + test("Method Overload", () => { + const testObj = java.newInstanceSync("Test"); + let result = testObj.methodOverloadSync("a"); + expect(result).toBe(1); + result = testObj.methodOverloadSync(1); + expect(result).toBe(2); + result = testObj.methodOverloadSync(java.newInstanceSync("Test$SuperClass")); + expect(result).toBe(3); + result = testObj.methodOverloadSync(java.newInstanceSync("Test$SubClass")); + expect(result).toBe(4); + }); + + test("Char array", () => { + const originalArray = "hello 世界\n".split(""); + const Arrays = java.import("java.util.Arrays"); + const arr1 = java.newArray("char", originalArray); + const list = Arrays.asListSync(arr1); + const arr2 = list.toArraySync(); + expect(arr2.length).toBe(1); + expect(arr2[0].length).toBe(9); + const isTypedArrayReturn = !(typeof arr2[0][0] === "string"); + for (let i = 0; i < originalArray.length; i++) { + if (isTypedArrayReturn) { + expect(arr2[0][i]).toBe(originalArray[i].charCodeAt(0)); + } else { + expect(arr2[0][i]).toBe(originalArray[i]); + } + } + }); +}); diff --git a/test/varargs.test.ts b/test/varargs.test.ts new file mode 100644 index 00000000..991229e5 --- /dev/null +++ b/test/varargs.test.ts @@ -0,0 +1,88 @@ +import { beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("varargs", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(); + }); + + test("array signature inference", () => { + const Test = java.import("Test"); + expect(Test.varArgsSignatureSync([])).toBe("Object..."); + expect(Test.varArgsSignatureSync(["a", "b"])).toBe("String..."); + expect(Test.varArgsSignatureSync([1, 2])).toBe("Integer..."); + expect(Test.varArgsSignatureSync([1.1, 2])).toBe("Number..."); + expect(Test.varArgsSignatureSync([1.1, "a"])).toBe("Object..."); + expect(Test.varArgsSignatureSync([true, "a"])).toBe("Object..."); + expect(Test.varArgsSignatureSync([true, 1])).toBe("Object..."); + expect(Test.varArgsSignatureSync([true, 1.1])).toBe("Object..."); + expect(Test.varArgsSignatureSync([true, false])).toBe("Boolean..."); + }); + + test("variadic signature inference", () => { + const Test = java.import("Test"); + expect(Test.varArgsSignatureSync()).toBe("Object..."); + expect(Test.varArgsSignatureSync("a", "b")).toBe("String..."); + expect(Test.varArgsSignatureSync(1, 2)).toBe("Integer..."); + expect(Test.varArgsSignatureSync(1.1, 2)).toBe("Number..."); + expect(Test.varArgsSignatureSync(1.1, "a")).toBe("Object..."); + expect(Test.varArgsSignatureSync(true, "a")).toBe("Object..."); + expect(Test.varArgsSignatureSync(true, 1)).toBe("Object..."); + expect(Test.varArgsSignatureSync(true, 1.1)).toBe("Object..."); + expect(Test.varArgsSignatureSync(true, false)).toBe("Boolean..."); + }); + + test("variadic no args", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("nothing")).toBe("nothing"); + }); + + test("variadic one args", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("%s", "hello")).toBe("hello"); + }); + + test("variadic two args", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("newArray(Object) no args passed", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("nothing", java.newArray("java.lang.Object", []))).toBe("nothing"); + }); + + test("newArray(Object) one args", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("%s", java.newArray("java.lang.Object", ["hello"]))).toBe("hello"); + }); + + test("newArray(Object) two args", () => { + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", java.newArray("java.lang.Object", ["hello", "world"]))).toBe("hello--world"); + }); + + test("Call static method with variadic varargs", () => { + const Test = java.import("Test"); + expect(Test.staticVarargsSync(5)).toBe("5"); + expect(Test.staticVarargsSync(5, "a")).toBe("5a"); + expect(Test.staticVarargsSync(5, "a", "b")).toBe("5ab"); + expect(Test.staticVarargsSync(5, "a", "b", "c")).toBe("5abc"); + }); + + test("Call static varargs method with plain array", () => { + const Test = java.import("Test"); + expect(Test.staticVarargsSync(5, ["a"])).toBe("5a"); + expect(Test.staticVarargsSync(5, ["a", "b"])).toBe("5ab"); + expect(Test.staticVarargsSync(5, ["a", "b", "c"])).toBe("5abc"); + }); + + test("Call static varags method with newArray", () => { + const Test = java.import("Test"); + expect(Test.staticVarargsSync(5, java.newArray("java.lang.String", ["a"]))).toBe("5a"); + expect(Test.staticVarargsSync(5, java.newArray("java.lang.String", ["a", "b", "c"]))).toBe("5abc"); + }); +}); diff --git a/testAsyncOptions/allThreeSuffix.test.ts b/testAsyncOptions/allThreeSuffix.test.ts new file mode 100644 index 00000000..72ebe81c --- /dev/null +++ b/testAsyncOptions/allThreeSuffix.test.ts @@ -0,0 +1,98 @@ +// All three variants have non-empty suffix, i.e a suffix is required for any variant. + +import { Java } from "../java"; +import { getJava } from "../testHelpers"; +import { describe, test, expect, beforeAll } from "vitest"; + +describe("allThreeSuffix", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava({ + syncSuffix: "Sync", + asyncSuffix: "Async", + promiseSuffix: "Promise", + }); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeDefined(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.addSync !== "undefined", "Expected `addSync` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.addAsync !== "undefined", "Expected `addAsync` to be present, but it is NOT.").toBeTruthy(); + expect( + typeof arrayList.addPromise !== "undefined", + "Expected `addPromise` to be present, but it is NOT." + ).toBeTruthy(); + expect(typeof arrayList.add === "undefined", "Expected `add` to NOT be present, but it is.").toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.sizeSync()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("formatSync"), "Expected `formatSync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatPromise"), "Expected `formatPromise` to be present, but it is NOT.").toBeTruthy(); + expect(!api.includes("format"), "Expected `format` to NOT be present, but it is.").toBeTruthy(); + expect(!api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeTruthy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addSync("hello"); + arrayList.addSync("world"); + expect(arrayList.sizeSync()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("asyncCalls", async () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + await new Promise((resolve) => { + arrayList.addAsync("hello", (err: Error | undefined) => { + expect(err).toBeUndefined(); + arrayList.addAsync("world", (err: Error | undefined) => { + expect(err).toBeUndefined(); + arrayList.sizeAsync((err: Error | undefined, size: number) => { + expect(err).toBeUndefined(); + expect(size).toBe(2); + resolve(); + }); + }); + }); + }); + }); + + test("promiseCalls", async () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + await arrayList + .addPromise("hello") + .then(() => { + return arrayList.addPromise("world"); + }) + .then(() => { + return arrayList.sizePromise(); + }) + .then((size: number) => { + expect(size).toBe(2); + }); + }); +}); diff --git a/testAsyncOptions/asyncSuffixSyncDefault.test.ts b/testAsyncOptions/asyncSuffixSyncDefault.test.ts new file mode 100644 index 00000000..6504c1d6 --- /dev/null +++ b/testAsyncOptions/asyncSuffixSyncDefault.test.ts @@ -0,0 +1,150 @@ +// Use "Async" for the asyncSuffix, and "" for the syncSuffix. + +import { beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("asyncSuffixSyncDefault", () => { + let java!: Java; + + beforeAll(async () => { + let beforeCalled = false; + let afterCalled = false; + + java = await getJava( + { + syncSuffix: "", + asyncSuffix: "Async", + ifReadOnlySuffix: "_alt", + }, + { + beforeInit: async (java) => { + function before(callback: () => void): void { + beforeCalled = true; + java.classpath.push("test/"); + expect(java.isJvmCreated()).toBeFalsy(); + callback(); + } + + function after(callback: () => void): void { + afterCalled = true; + expect(java.isJvmCreated()).toBeTruthy(); + callback(); + } + + java.registerClient(before, after); + + await new Promise((resolve) => { + java.ensureJvm((err) => { + expect(err).toBeFalsy(); + expect(java.isJvmCreated()).toBeTruthy(); + resolve(); + }); + }); + }, + } + ); + + expect(beforeCalled).toBeTruthy(); + expect(afterCalled).toBeTruthy(); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.addAsync !== "undefined", "Expected `addAsync` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.add !== "undefined", "Expected `add` to be present, but it is NOT.").toBeTruthy(); + expect( + typeof arrayList.addPromise === "undefined", + "Expected `addPromise` to NOT be present, but it is." + ).toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.size()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("format"), "Expected `format` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to be present, but it is NOT.").toBeTruthy(); + expect(!api.includes("formatSync"), "Expected `formatSync` to NOT be present, but it is.").toBeTruthy(); + expect(!api.includes("formatPromise"), "Expected `formatPromise` to NOT be present, but it is.").toBeTruthy(); + expect(!api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeTruthy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.add("hello"); + arrayList.add("world"); + expect(arrayList.size()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.format("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("asyncCalls", async () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + await new Promise((resolve) => { + arrayList.addAsync("hello", (err: Error | undefined) => { + expect(err).toBeUndefined(); + arrayList.addAsync("world", (err: Error | undefined) => { + expect(err).toBeUndefined(); + arrayList.sizeAsync((err: Error | undefined, size: number | undefined) => { + expect(err).toBeUndefined(); + expect(size).toBe(2); + resolve(); + }); + }); + }); + }); + }); + + // See testUnusableMethodName.js for the purpose of these last two tests. + // In that test, Test.name_alt() is an async method. + // In this test, it is a sync method. + test("unusableMethodNameThrows", () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + expect(() => Test.name()).toThrowError(TypeError); + }); + + test("alternateMethodNameWorks", () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + expect(Test.name_alt()).toBe("name"); + expect(Test.caller_alt()).toBe("caller"); + expect(Test.arguments_alt()).toBe("arguments"); + }); + + test("reservedFieldName", () => { + const TestEnum = java.import("Test$Enum"); + expect(TestEnum).toBeTruthy(); + + // 'foo' and 'bar' are valid enum names + expect(TestEnum.foo.toString()).toBe("foo"); + expect(TestEnum.bar.toString()).toBe("bar"); + + // TestEnum.name is actually the name of the proxy constructor function. + expect(TestEnum.name).toBe("javaClassConstructorProxy"); + + // Instead we need to access TestEnum.name_alt + expect(TestEnum.name_alt.toString()).toBe("name"); + expect(TestEnum.caller_alt.toString()).toBe("caller"); + expect(TestEnum.arguments_alt.toString()).toBe("arguments"); + }); +}); diff --git a/testAsyncOptions/clientBeforeError.test.ts b/testAsyncOptions/clientBeforeError.test.ts new file mode 100644 index 00000000..187e13a2 --- /dev/null +++ b/testAsyncOptions/clientBeforeError.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("clientBeforeError", () => { + test("clientBeforeError", async () => { + await getJava( + { + syncSuffix: "Sync", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function before(callback: (err: Error) => void): void { + expect(java.isJvmCreated()).toBeFalsy(); + callback(new Error("dummy error")); + } + + java.registerClient(before); + + await new Promise((resolve) => { + java.ensureJvm((err) => { + expect(err).toBeTruthy(); + expect(typeof err).toBe("object"); + expect(err).toBeInstanceOf(Error); + expect(err?.message).toBe("dummy error"); + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + }); + }); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/clientBeforeSyncThrows.test.ts b/testAsyncOptions/clientBeforeSyncThrows.test.ts new file mode 100644 index 00000000..5277fc17 --- /dev/null +++ b/testAsyncOptions/clientBeforeSyncThrows.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("clientBeforeSyncThrows", () => { + test("clientBeforeSyncThrows", async () => { + await getJava( + { + syncSuffix: "Sync", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function before(): void { + expect(java.isJvmCreated()).toBeFalsy(); + throw new Error("dummy error"); + } + java.registerClient(before); + + await new Promise((resolve) => { + java.ensureJvm((err: Error | undefined) => { + expect(err && typeof err === "object").toBeTruthy(); + expect(err).instanceOf(Error); + expect(err?.message).toBe("dummy error"); + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + }); + }); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/clientBeforeThrows.test.ts b/testAsyncOptions/clientBeforeThrows.test.ts new file mode 100644 index 00000000..ef493fa7 --- /dev/null +++ b/testAsyncOptions/clientBeforeThrows.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("clientBeforeThrows", () => { + test("clientBeforeThrows", async () => { + await getJava( + { + syncSuffix: "Sync", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function before(): void { + expect(java.isJvmCreated()).toBeFalsy(); + throw new Error("dummy error"); + } + java.registerClient(before); + + await new Promise((resolve) => { + java.ensureJvm((err: Error | undefined) => { + expect(err && typeof err === "object").toBeTruthy(); + expect(err).instanceOf(Error); + expect(err?.message).toBe("dummy error"); + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + }); + }); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/clientPBeforeError.test.ts b/testAsyncOptions/clientPBeforeError.test.ts new file mode 100644 index 00000000..0d8e4986 --- /dev/null +++ b/testAsyncOptions/clientPBeforeError.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("clientPBeforeError", () => { + test("clientPBeforeError", async () => { + await getJava( + { + syncSuffix: "Sync", + promiseSuffix: "Promise", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function beforeP(): Promise { + return new Promise((_resolve, reject) => { + expect(java.isJvmCreated()).toBeFalsy(); + reject(new Error("dummy error")); + }); + } + java.registerClientP(beforeP); + + await new Promise((resolve) => { + java.ensureJvm().then( + () => { + throw new Error("expect error"); + }, + (err: Error | undefined) => { + expect(err && typeof err === "object").toBeTruthy(); + expect(err).instanceOf(Error); + expect(err?.message).toBe("dummy error"); + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + } + ); + }); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/clientPBeforeThrows.test.ts b/testAsyncOptions/clientPBeforeThrows.test.ts new file mode 100644 index 00000000..cb7d725a --- /dev/null +++ b/testAsyncOptions/clientPBeforeThrows.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("clientPBeforeThrows", () => { + test("clientPBeforeThrows", async () => { + await getJava( + { + syncSuffix: "Sync", + promiseSuffix: "Promise", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function beforeP(): Promise { + return new Promise(() => { + expect(java.isJvmCreated()).toBeFalsy(); + throw new Error("dummy error"); + }); + } + + java.registerClientP(beforeP); + + await new Promise((resolve) => { + java.ensureJvm().then( + () => { + throw new Error("expected error"); + }, + (err: Error | undefined) => { + expect(err && typeof err === "object").toBeTruthy(); + expect(err).instanceOf(Error); + expect(err?.message).toBe("dummy error"); + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + } + ); + }); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/defacto.test.ts b/testAsyncOptions/defacto.test.ts new file mode 100644 index 00000000..e9fc148e --- /dev/null +++ b/testAsyncOptions/defacto.test.ts @@ -0,0 +1,113 @@ +// In the defacto case, the developer sets asyncOptions, but specifies the defacto standard behavior. + +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("defacto", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava( + { + syncSuffix: "Sync", + asyncSuffix: "", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function before(): void { + expect(java.isJvmCreated()).toBeFalsy(); + } + + function after(): void { + expect(java.isJvmCreated()).toBeTruthy(); + } + + java.registerClient(before, after); + java.registerClient(undefined, after); + java.registerClient(before, undefined); + + await new Promise((resolve) => { + java.ensureJvm(function (err) { + expect(err).toBeFalsy(); + expect(java.isJvmCreated()).toBeTruthy(); + + // Verify that ensureJvm is idempotent + java.ensureJvm(function (err) { + expect(err).toBeFalsy(); + resolve(); + }); + }); + }); + }, + } + ); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.addSync !== "undefined", "Expected `addSync` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.add !== "undefined", "Expected `add` to be present, but it is NOT.").toBeTruthy(); + expect( + typeof arrayList.addPromise === "undefined", + "Expected `addPromise` to NOT be present, but it is." + ).toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.sizeSync()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("format"), "Expected `format` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatSync"), "Expected `formatSync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatPromise"), "Expected `formatPromise` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeFalsy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addSync("hello"); + arrayList.addSync("world"); + expect(arrayList.sizeSync()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("asyncCalls", async () => { + await new Promise((resolve) => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.add("hello", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.add("world", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.size((err: Error | undefined, size: number | undefined) => { + expect(err).toBeFalsy(); + expect(size).toBe(2); + resolve(); + }); + }); + }); + }); + }); +}); diff --git a/testAsyncOptions/defactoPlusPromise.test.ts b/testAsyncOptions/defactoPlusPromise.test.ts new file mode 100644 index 00000000..d41a4b2b --- /dev/null +++ b/testAsyncOptions/defactoPlusPromise.test.ts @@ -0,0 +1,121 @@ +// The defacto case but with promises also enabled. + +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("defactoPlusPromise", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava( + { + syncSuffix: "Sync", + asyncSuffix: "", + promiseSuffix: "Promise", + }, + { + beforeInit: (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function before(callback: () => void): void { + expect(java.isJvmCreated()).toBeFalsy(); + callback(); + } + + function after(callback: () => void): void { + expect(java.isJvmCreated()).toBeTruthy(); + callback(); + } + + java.registerClient(before, after); + java.registerClient(null, after); + java.registerClient(before); + }, + } + ); + + await java.ensureJvm(); + expect(java.isJvmCreated()).toBeTruthy(); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(arrayList.addSync !== "undefined", "Expected `addSync` to be present, but it is NOT.").toBeTruthy(); + expect(arrayList.add !== "undefined", "Expected `add` to be present, but it is NOT.").toBeTruthy(); + expect(arrayList.addPromise !== "undefined", "Expected `addPromise` to be present, but it is NOT.").toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.sizeSync()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("format"), "Expected `format` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatSync"), "Expected `formatSync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatPromise"), "Expected `formatPromise` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeFalsy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addSync("hello"); + arrayList.addSync("world"); + expect(arrayList.sizeSync()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("asyncCalls", async () => { + await new Promise((resolve) => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.add("hello", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.add("world", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.size((err: Error | undefined, size: number | undefined) => { + expect(err).toBeFalsy(); + expect(size).toBe(2); + resolve(); + }); + }); + }); + }); + }); + + test("promiseCalls", async () => { + await new Promise((resolve) => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList + .addPromise("hello") + .then(() => { + return arrayList.addPromise("world"); + }) + .then(() => { + return arrayList.sizePromise(); + }) + .then((size: number) => { + expect(size).toBe(2); + resolve(); + }); + }); + }); +}); diff --git a/testAsyncOptions/default.test.ts b/testAsyncOptions/default.test.ts new file mode 100644 index 00000000..ab7766d1 --- /dev/null +++ b/testAsyncOptions/default.test.ts @@ -0,0 +1,83 @@ +// In the default case, the developer does not set asyncOptions. +// We should get the defacto standard behavior. + +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("default", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava(null); + }); + + afterAll(() => { + java.stop(); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.addSync !== "undefined", "Expected `addSync` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.add !== "undefined", "Expected `add` to be present, but it is NOT.").toBeTruthy(); + expect( + typeof arrayList.addPromise === "undefined", + "Expected `addPromise` to NOT be present, but it is." + ).toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.sizeSync()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("format"), "Expected `format` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatSync"), "Expected `formatSync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatPromise"), "Expected `formatPromise` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeFalsy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addSync("hello"); + arrayList.addSync("world"); + expect(arrayList.sizeSync()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("asyncCalls", async () => { + await new Promise((resolve) => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.add("hello", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.add("world", (err: Error | undefined) => { + expect(err).toBeFalsy(); + arrayList.size((err: Error | undefined, size: number | undefined) => { + expect(err).toBeFalsy(); + expect(size).toBe(2); + resolve(); + }); + }); + }); + }); + }); +}); diff --git a/testAsyncOptions/ensureJvmPromise.test.ts b/testAsyncOptions/ensureJvmPromise.test.ts new file mode 100644 index 00000000..6e56a711 --- /dev/null +++ b/testAsyncOptions/ensureJvmPromise.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("ensureJvmPromise", () => { + test("calling ensureJvm as a promise", async () => { + await getJava( + { + syncSuffix: "Sync", + asyncSuffix: "", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + await java.ensureJvm(); + expect(java.isJvmCreated()).toBeTruthy(); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/invalidLaunch.test.ts b/testAsyncOptions/invalidLaunch.test.ts new file mode 100644 index 00000000..53fefb7c --- /dev/null +++ b/testAsyncOptions/invalidLaunch.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; + +describe("invalidLaunch", () => { + test("callbackNotAFunction", async () => { + await getJava( + { + syncSuffix: "", + promiseSuffix: "P", + }, + { + beforeInit: (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + expect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (java as any).ensureJvm("foo"); + }).toThrow(/requires its one argument to be a callback function/); + + expect(java.isJvmCreated()).toBeFalsy(); + }, + } + ); + }); + + test("jvmCanStillBeLaunched", async () => { + await getJava( + { + syncSuffix: "", + promiseSuffix: "P", + }, + { + beforeInit: async (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + await java.ensureJvm(); + expect(java.isJvmCreated()).toBeTruthy(); + }, + } + ); + }); +}); diff --git a/testAsyncOptions/noAsync.test.ts b/testAsyncOptions/noAsync.test.ts new file mode 100644 index 00000000..d3b16e26 --- /dev/null +++ b/testAsyncOptions/noAsync.test.ts @@ -0,0 +1,111 @@ +// Just Sync and Promise, both with a non-empty suffix. + +import { beforeAll, describe, expect, test } from "vitest"; +import { Java } from "../java"; +import { getJava } from "../testHelpers"; + +describe("noAsync", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava( + { + syncSuffix: "Sync", + promiseSuffix: "Promise", + }, + { + beforeInit: (java) => { + expect(java.isJvmCreated()).toBeFalsy(); + + function beforeP(): Promise { + return new Promise((resolve) => { + expect(java.isJvmCreated()).toBeFalsy(); + resolve(); + }); + } + + function afterP(): Promise { + return new Promise((resolve) => { + expect(java.isJvmCreated()).toBeTruthy(); + resolve(); + }); + } + + java.registerClientP(beforeP, afterP); + java.registerClientP(null, afterP); + java.registerClientP(beforeP); + }, + } + ); + + await java.ensureJvm(); + expect(java.isJvmCreated()).toBeTruthy(); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.addSync !== "undefined", "Expected `addSync` to be present, but it is NOT.").toBeTruthy(); + expect( + typeof arrayList.addPromise !== "undefined", + "Expected `addPromise` to be present, but it is NOT." + ).toBeTruthy(); + expect(typeof arrayList.add === "undefined", "Expected `add` to NOT be present, but it is.").toBeTruthy(); + expect(typeof arrayList.addAsync === "undefined", "Expected `addAsync` to NOT be present, but it is.").toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.sizeSync()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("formatSync"), "Expected `formatSync` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatPromise"), "Expected `formatPromise` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("format"), "Expected `format` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeFalsy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addSync("hello"); + arrayList.addSync("world"); + expect(arrayList.sizeSync()).toBe(2); + }); + + test("sStaticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.formatSync("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("promiseCalls", async () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + await new Promise((resolve) => { + arrayList + .addPromise("hello") + .then(() => { + return arrayList.addPromise("world"); + }) + .then(() => { + return arrayList.sizePromise(); + }) + .then((size: number) => { + expect(size).toBe(2); + resolve(); + }); + }); + }); +}); diff --git a/testAsyncOptions/syncDefaultPlusPromise.test.ts b/testAsyncOptions/syncDefaultPlusPromise.test.ts new file mode 100644 index 00000000..21e214ff --- /dev/null +++ b/testAsyncOptions/syncDefaultPlusPromise.test.ts @@ -0,0 +1,82 @@ +// Just Sync and Promise, with Sync the default (i.e. no suffix). +// This is the configuration that RedSeal wants for use with Tinkerpop/Gremlin. + +import { beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("syncDefaultPlusPromise", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava({ + syncSuffix: "", + promiseSuffix: "P", + }); + }); + + test("api", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + expect(arrayList).toBeTruthy(); + expect(java.instanceOf(arrayList, "java.util.ArrayList")).toBeTruthy(); + + expect(typeof arrayList.add !== "undefined", "Expected `add` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.addP !== "undefined", "Expected `addP` to be present, but it is NOT.").toBeTruthy(); + expect(typeof arrayList.addSync === "undefined", "Expected `addSync` to NOT be present, but it is.").toBeTruthy(); + expect(typeof arrayList.addAsync === "undefined", "Expected `addAsync` to NOT be present, but it is.").toBeTruthy(); + }); + + test("importClass", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // This test verifies the import runs without error. + const ArrayList = java.import("java.util.ArrayList"); + expect(ArrayList).toBeTruthy(); + const arrayList = new ArrayList(); + expect(arrayList).toBeTruthy(); + expect(arrayList.size()).toBe(0); + }); + + test("staticAPI", () => { + const String = java.import("java.lang.String"); + expect(String).toBeTruthy(); + + const api = Object.keys(String).filter((key) => typeof String[key] === "function"); + expect(api.includes("format"), "Expected `format` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatP"), "Expected `formatP` to be present, but it is NOT.").toBeTruthy(); + expect(api.includes("formatSync"), "Expected `formatSync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatAsync"), "Expected `formatAsync` to NOT be present, but it is.").toBeFalsy(); + expect(api.includes("formatundefined"), "Expected `formatundefined` to NOT be present, but it is.").toBeFalsy(); + }); + + test("syncCalls", () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.add("hello"); + arrayList.add("world"); + expect(arrayList.size()).toBe(2); + }); + + test("staticSyncCalls", () => { + // Note: java.import executes javascript code in src-node/nodeJavaBridge that makes sync calls to java classes. + // Among other things, java.import creates Sync functions for static methods. + const String = java.import("java.lang.String"); + expect(String.format("%s--%s", "hello", "world")).toBe("hello--world"); + }); + + test("promiseCalls", async () => { + const arrayList = java.newInstanceSync("java.util.ArrayList"); + await new Promise((resolve) => { + arrayList + .addP("hello") + .then(() => { + return arrayList.addP("world"); + }) + .then(() => { + return arrayList.sizeP(); + }) + .then((size: number) => { + expect(size).toBe(2); + resolve(); + }); + }); + }); +}); diff --git a/testAsyncOptions/unusableMethodName.test.ts b/testAsyncOptions/unusableMethodName.test.ts new file mode 100644 index 00000000..66232bab --- /dev/null +++ b/testAsyncOptions/unusableMethodName.test.ts @@ -0,0 +1,131 @@ +// For any function, the property 'name' is an unwritable property. +// The value returned by java.import() is a constructor-like function that has the shape of the class. +// In particular, any static methods of the class will be added as properties of the function. +// If a class has a static method named 'name', then an exception woudld be thrown when +// node-java attempts to set assign the static method to the .name property of constructor-like function. +// As a workaround, node-java will append the `ifReadOnlySuffix` to the property name. + +import { beforeAll, describe, expect, test } from "vitest"; +import { getJava } from "../testHelpers"; +import { Java } from "../java"; + +describe("unusableMethodName", () => { + let java!: Java; + + beforeAll(async () => { + java = await getJava( + { + syncSuffix: "Sync", + asyncSuffix: "", + ifReadOnlySuffix: "_alt", + }, + { + beforeInit: (java) => { + function before(callback: () => void): void { + java.classpath.push("test/"); + expect(java.isJvmCreated()).toBeFalsy(); + callback(); + } + + function after(callback: () => void): void { + expect(java.isJvmCreated()).toBeTruthy(); + callback(); + } + + java.registerClient(before, after); + }, + } + ); + + await new Promise((resolve) => { + java.ensureJvm(function (err: Error | undefined) { + expect(err).toBeFalsy(); + expect(java.isJvmCreated()).toBeTruthy(); + resolve(); + }); + }); + }); + + test("unusableMethodName_nameThrows", () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + expect(() => { + Test.name((_err: Error | undefined) => { + throw new Error("should not get here"); + }); + }).toThrowError(TypeError); + }); + + test("unusableMethodName_callerThrows", () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + expect(() => { + Test.caller((_err: Error | undefined) => { + throw new Error("should not get here"); + }); + }).toThrowError(TypeError); + }); + + test("unusableMethodName_argumentsThrows", () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + expect(() => { + Test.arguments((_err: Error | undefined) => { + throw new Error("should not get here"); + }); + }).toThrowError(TypeError); + }); + + test("alternateMethodName_name_altWorks", async () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + await new Promise((resolve) => { + Test.name_alt((err: Error | undefined, val: string | undefined) => { + expect(err).toBeFalsy(); + expect(val).toBe("name"); + resolve(); + }); + }); + }); + + test("alternateMethodName_caller_altWorks", async () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + await new Promise((resolve) => { + Test.caller_alt((err: Error | undefined, val: string | undefined) => { + expect(err).toBeFalsy(); + expect(val).toBe("caller"); + resolve(); + }); + }); + }); + + test("alternateMethodName_arguments_altWorks", async () => { + const Test = java.import("Test"); + expect(Test).toBeTruthy(); + await new Promise((resolve) => { + Test.arguments_alt((err: Error | undefined, val: string | undefined) => { + expect(err).toBeFalsy(); + expect(val).toBe("arguments"); + resolve(); + }); + }); + }); + + test("reservedFieldName", () => { + const TestEnum = java.import("Test$Enum"); + expect(TestEnum).toBeTruthy(); + + // 'foo' and 'bar' are valid enum names + expect(TestEnum.foo.toStringSync()).toBe("foo"); + expect(TestEnum.bar.toStringSync()).toBe("bar"); + + // TestEnum.name is actually the name of the proxy constructor function. + expect(TestEnum.name).toBe("javaClassConstructorProxy"); + + // Instead we need to access TestEnum.name_alt + expect(TestEnum.name_alt.toString()).toBe("name"); + expect(TestEnum.caller_alt.toString()).toBe("caller"); + expect(TestEnum.arguments_alt.toString()).toBe("arguments"); + }); +}); diff --git a/testHelpers.js b/testHelpers.js deleted file mode 100644 index 01668c23..00000000 --- a/testHelpers.js +++ /dev/null @@ -1,7 +0,0 @@ - -var java = require("./"); -java.options.push("-Djava.awt.headless=true"); -java.classpath.push("test/"); -java.classpath.push("test/commons-lang3-3.1.jar"); - -module.exports.java = java; diff --git a/testHelpers.ts b/testHelpers.ts new file mode 100644 index 00000000..f5543a14 --- /dev/null +++ b/testHelpers.ts @@ -0,0 +1,44 @@ +import { AsyncOptions, Java, JavaError } from "./java"; +import findRoot from "find-root"; + +const root = findRoot(__dirname); +let java: Promise | undefined; + +export interface GetJavaOptions { + beforeInit?: (java: Java) => void | Promise; +} + +export async function getJava(asyncOptions?: AsyncOptions | null, options?: GetJavaOptions): Promise { + if (java) { + return java; + } + java = _getJava(asyncOptions, options); + return java; +} + +async function _getJava(asyncOptions?: AsyncOptions | null, options?: GetJavaOptions): Promise { + const java = (await import(root)).default as Java; + + java.options.push("-Djava.awt.headless=true"); + //java.options.push('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005'); + + java.classpath.push("test/"); + java.classpath.push("test/commons-lang3-3.18.0.jar"); + + if (asyncOptions !== null) { + java.asyncOptions = asyncOptions ?? { + syncSuffix: "Sync", + asyncSuffix: "", + promiseSuffix: "Promise", + }; + } + + await options?.beforeInit?.(java); + + // force initialization + java.import("java.util.ArrayList"); + + return java; +} + +export function expectJavaError(error: unknown): asserts error is JavaError {} diff --git a/testIntegration/jdbc/enduranceTest.js b/testIntegration/jdbc/enduranceTest.js new file mode 100644 index 00000000..e1b2e0a6 --- /dev/null +++ b/testIntegration/jdbc/enduranceTest.js @@ -0,0 +1,81 @@ +"use strict"; + +const memwatch = require("memwatch"); + +const dbServerName = "192.168.13.190"; +const dbPort = 1433; +const dbName = "test"; +const dbUserId = "test"; +const dbPassword = "test"; +const dbConnectString = + "jdbc:sqlserver://" + + dbServerName + + ":" + + dbPort + + ";databaseName=" + + dbName + + ";selectMethod=direct;responseBuffering=adaptive;packetSize=0;programName=nodeJavaTest;hostProcess=nodeJavaTest;sendStringParametersAsUnicode=false;"; +const dbConnectionClass = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + +//const dbUserId = 'test'; +//const dbPassword = 'test'; +//const dbConnectString = "jdbc:mysql://localhost/test"; +//const dbConnectionClass = 'com.mysql.jdbc.Driver'; + +const util = require("util"); +const path = require("path"); +const java = require("../../"); +java.classpath.push(path.join(__dirname, "sqljdbc4.jar")); +java.classpath.push(path.join(__dirname, "mysql-connector-java-5.1.22-bin.jar")); +const DriverManager = java.import("java.sql.DriverManager"); + +setTimeout(function () { + console.log("start heap diff"); + const hd = new memwatch.HeapDiff(); + const loopStart = new Date(); + for (let loopCount = 0; loopCount < 500000; loopCount++) { + console.log("loopCount:", loopCount); + doLoop(); + } + const loopEnd = new Date(); + console.log("end loop", loopEnd - loopStart); + memwatch.gc(); + const diff = hd.end(); + console.log(util.inspect(diff.change, false, 10, true)); + + console.log("done... waiting 30seconds"); + setTimeout(function () { + console.log("really done"); + }, 30 * 1000); +}, 1); + +function doLoop() { + java.findClassSync(dbConnectionClass); + const conn = DriverManager.getConnectionSync(dbConnectString, dbUserId, dbPassword); + //console.log("connected"); + const statement = conn.createStatementSync(); + const queryString = "select * from Person"; + const rs = statement.executeQuerySync(queryString); + const metaData = rs.getMetaDataSync(); + const columnCount = metaData.getColumnCountSync(); + while (rs.nextSync()) { + for (let i = 1; i <= columnCount; i++) { + const obj = rs.getObjectSync(i); + if (obj) { + if (Object.prototype.hasOwnProperty.call(obj, "getClassSync")) { + if (obj.getClassSync().toString() == "class java.math.BigDecimal") { + //console.log(obj.doubleValueSync()); + continue; + } + if (obj.getClassSync().toString() == "class java.sql.Timestamp") { + //console.log(obj.getTimeSync()); + continue; + } + //console.log("class:", obj.getClassSync().toString()); + } + //console.log(obj); + } + } + } + conn.closeSync(); +} diff --git a/testIntegration/jdbc/enduranceTestAsync.js b/testIntegration/jdbc/enduranceTestAsync.js new file mode 100644 index 00000000..87b8eea3 --- /dev/null +++ b/testIntegration/jdbc/enduranceTestAsync.js @@ -0,0 +1,140 @@ +"use strict"; + +// Tests concurrent async jdbc connections. +// TODO: a proper jdbc wrapper should be created to avoid ugly async code. + +const memwatch = require("memwatch"); +const async = require("async"); + +// 1 - 23,305ms +// 5 - 9,338ms +// 10 - 8,846ms +const concurrency = 5; + +//const dbServerName = '192.168.13.190'; +//const dbPort = 1433; +//const dbName = 'test'; +//const dbUserId = 'test'; +//const dbPassword = 'test'; +//const dbConnectString = 'jdbc:sqlserver://' + dbServerName + ':' + dbPort + ';databaseName=' + dbName + ';selectMethod=direct;responseBuffering=adaptive;packetSize=0;programName=nodeJavaTest;hostProcess=nodeJavaTest;sendStringParametersAsUnicode=false;'; +//const dbConnectionClass = 'com.microsoft.sqlserver.jdbc.SQLServerDriver'; + +const dbUserId = "test"; +const dbPassword = "test"; +const dbConnectString = "jdbc:mysql://localhost/test"; +const dbConnectionClass = "com.mysql.jdbc.Driver"; + +const util = require("util"); +const path = require("path"); +const java = require("../../"); +java.classpath.push(path.join(__dirname, "sqljdbc4.jar")); +java.classpath.push(path.join(__dirname, "mysql-connector-java-5.1.22-bin.jar")); +const DriverManager = java.import("java.sql.DriverManager"); + +setTimeout(function () { + console.log("start heap diff"); + const hd = new memwatch.HeapDiff(); + const loopStart = new Date(); + + java.findClassSync(dbConnectionClass); + + const loopIterations = []; + for (let i = 0; i < 5000; i++) { + loopIterations.push(i); + } + + async.forEachLimit( + loopIterations, + concurrency, + function (loopCount, callback) { + console.log("loopCount:", loopCount); + return doLoop(callback); + }, + function (err) { + if (err) { + console.log("fail", err); + } + const loopEnd = new Date(); + console.log("end loop", loopEnd - loopStart); + memwatch.gc(); + const diff = hd.end(); + console.log(util.inspect(diff.change, false, 10, true)); + + console.log("done... waiting 30seconds"); + setTimeout(function () { + console.log("really done"); + }, 30 * 1000); + } + ); +}, 1); + +function doLoop(callback) { + let conn; + + return DriverManager.getConnection(dbConnectString, dbUserId, dbPassword, getConnectionComplete); + + function getConnectionComplete(err, _conn) { + if (err) { + return callback(err); + } + conn = _conn; + + //console.log("connected"); + const queryString = "select * from Person"; + return executeQuery( + conn, + queryString, + function (row, callback) { + //console.log("row", row); + return callback(); + }, + function (err) { + if (err) { + return callback(err); + } + //console.log("query complete"); + return callback(); + } + ); + } +} + +function executeQuery(conn, sql, rowCallback, completeCallback) { + const statement = conn.createStatementSync(); + return statement.executeQuery(sql, function (err, rs) { + if (err) { + return completeCallback(err); + } + + const columnCount = rs.getMetaDataSync().getColumnCountSync(); + + let rsComplete = false; + async.until( + function () { + return rsComplete; + }, + function (callback) { + return rs.next(function (err, rsNextResult) { + if (err) { + return callback(err); + } + if (!rsNextResult) { + rsComplete = true; + return callback(); + } + const row = []; + for (let i = 1; i <= columnCount; i++) { + row.push(rs.getObjectSync(i)); + } + return rowCallback(row, callback); + }); + }, + function (err) { + if (err) { + return completeCallback(err); + } + return conn.close(completeCallback); + } + ); + }); +} diff --git a/testIntegration/jdbc/mysql-connector-java-5.1.22-bin.jar b/testIntegration/jdbc/mysql-connector-java-5.1.22-bin.jar new file mode 100644 index 00000000..a50424d6 Binary files /dev/null and b/testIntegration/jdbc/mysql-connector-java-5.1.22-bin.jar differ diff --git a/testIntegration/jdbc/sqljdbc.jar b/testIntegration/jdbc/sqljdbc.jar new file mode 100644 index 00000000..53c6667e Binary files /dev/null and b/testIntegration/jdbc/sqljdbc.jar differ diff --git a/testIntegration/jdbc/sqljdbc4.jar b/testIntegration/jdbc/sqljdbc4.jar new file mode 100644 index 00000000..d6b7f6da Binary files /dev/null and b/testIntegration/jdbc/sqljdbc4.jar differ diff --git a/testIntegration/poi/poi-3.9-20121203.jar b/testIntegration/poi/poi-3.9-20121203.jar new file mode 100644 index 00000000..0f462880 Binary files /dev/null and b/testIntegration/poi/poi-3.9-20121203.jar differ diff --git a/testIntegration/poi/poi-scratchpad-3.9-20121203.jar b/testIntegration/poi/poi-scratchpad-3.9-20121203.jar new file mode 100644 index 00000000..15208552 Binary files /dev/null and b/testIntegration/poi/poi-scratchpad-3.9-20121203.jar differ diff --git a/testIntegration/poi/poiTest.js b/testIntegration/poi/poiTest.js new file mode 100644 index 00000000..220a4a8a --- /dev/null +++ b/testIntegration/poi/poiTest.js @@ -0,0 +1,23 @@ +const java = require("../../"); +java.classpath.push("poi-3.9-20121203.jar"); +java.classpath.push("poi-scratchpad-3.9-20121203.jar"); + +const stream = java.newInstanceSync("java.io.FileInputStream", "presentation.ppt"); +const ppt = java.newInstanceSync("org.apache.poi.hslf.usermodel.SlideShow", stream); +stream.close(); + +const pgsize = ppt.getPageSizeSync(); +console.log(`found page size ${pgsize.width}x${pgsize.height}`); + +const slides = ppt.getSlidesSync(); +console.log(`found ${slides.length} slides`); + +const TYPE_INT_RGB = java.getStaticFieldValue("java.awt.image.BufferedImage", "TYPE_INT_RGB"); + +for (let i = 0; i < slides.length; i++) { + console.log(`creating image: ${i}`); + const img = java.newInstanceSync("java.awt.image.BufferedImage", pgsize.width, pgsize.height, TYPE_INT_RGB); + img.createGraphicsSync(); +} +console.log("done"); +process.exit(0); diff --git a/testIntegration/poi/presentation.ppt b/testIntegration/poi/presentation.ppt new file mode 100644 index 00000000..486fa130 Binary files /dev/null and b/testIntegration/poi/presentation.ppt differ diff --git a/testIntegration/webkit/index.html b/testIntegration/webkit/index.html new file mode 100644 index 00000000..28147d94 --- /dev/null +++ b/testIntegration/webkit/index.html @@ -0,0 +1,11 @@ + + + + Hello World! + + + +

Hello World!

+ We are using java: . + + diff --git a/testIntegration/webkit/main.js b/testIntegration/webkit/main.js new file mode 100644 index 00000000..2cf9063e --- /dev/null +++ b/testIntegration/webkit/main.js @@ -0,0 +1,5 @@ +const java = require("../../"); + +export function runJavaMethod() { + return java.callStaticMethodSync("java.lang.System", "getProperty", "java.version"); +} diff --git a/testIntegration/webkit/package.json b/testIntegration/webkit/package.json new file mode 100644 index 00000000..60e6599b --- /dev/null +++ b/testIntegration/webkit/package.json @@ -0,0 +1,6 @@ +{ + "name": "webkitIntegrationTest", + "version": "0.0.0", + "description": "", + "main": "index.html" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..4ed39cb0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,116 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./build/ts", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": [ + "examples" + ] +}