From 80b160c53e86f0f356f9f83fd520774dfeca19b0 Mon Sep 17 00:00:00 2001 From: Sam Cao Date: Mon, 30 Aug 2021 10:19:57 +0800 Subject: [PATCH] Javet v0.9.10 (#85) * Added `JavetVirtualObject` * Updated `JavetUniversalProxyHandler` to allow passing `V8Value` * Updated `JavetUniversalProxyHandler` to allow passing `V8ValueFunction` as anonymous function * Updated `JavetUniversalProxyHandler` to allow passing `V8ValueObject` as anonymous object * Added `isClosed()` to `IJavetClosable` * Added error code 602 and 603 --- README.rst | 12 +- build.gradle.kts | 2 +- cpp/build-linux.sh | 2 +- cpp/build-macos.sh | 2 +- cpp/build-windows.cmd | 2 +- cpp/jni/javet_resource_node.rc | 12 +- cpp/jni/javet_resource_v8.rc | 12 +- docker/windows-x86_64/base.Dockerfile | 110 +++- docker/windows-x86_64/build.Dockerfile | 43 ++ docs/development/build.rst | 2 +- docs/development/build_javet_with_docker.rst | 34 +- .../build_javet_with_pre_built_binaries.rst | 2 + docs/development/index.rst | 2 +- docs/reference/error_codes.rst | 2 + docs/release_notes.rst | 10 + docs/tutorial/installation.rst | 15 +- docs/tutorial/object_converter.rst | 130 ++++ pom.xml | 4 +- scripts/node/javet-rebuild/rebuild.cmd | 2 +- scripts/node/javet-rebuild/rebuild.sh | 2 +- scripts/python/change_javet_version.py | 15 +- .../caoccao/javet/exceptions/JavetError.java | 4 + .../javet/interfaces/IJavetClosable.java | 8 + .../caoccao/javet/interop/JavetLibLoader.java | 2 +- .../com/caoccao/javet/interop/V8Locker.java | 22 +- .../com/caoccao/javet/interop/V8Runtime.java | 13 +- .../javet/interop/engine/JavetEngine.java | 5 + .../interop/engine/JavetEngineGuard.java | 73 +++ .../javet/interop/engine/JavetEnginePool.java | 85 +++ ...ProxyV8ValueFunctionInvocationHandler.java | 69 +++ ...icProxyV8ValueObjectInvocationHandler.java | 69 +++ .../proxy/JavetUniversalProxyHandler.java | 570 ++++++++++-------- .../javet/node/modules/BaseNodeModule.java | 10 +- .../javet/utils/JavetResourceUtils.java | 10 +- .../javet/utils/JavetVirtualObject.java | 94 +++ .../com/caoccao/javet/utils/V8ValueUtils.java | 28 + .../com/caoccao/javet/values/V8Value.java | 5 + .../javet/values/reference/IV8ValueArray.java | 10 + .../values/reference/IV8ValueReference.java | 2 + .../values/reference/V8ValueReference.java | 9 +- .../javet/values/virtual/V8VirtualValue.java | 6 + .../values/virtual/V8VirtualValueList.java | 7 + .../javet/interop/TestNodeRuntime.java | 1 + .../caoccao/javet/interop/TestV8Locker.java | 2 + .../caoccao/javet/interop/TestV8Runtime.java | 4 + .../converters/TestJavetProxyConverter.java | 113 +++- .../interop/engine/TestJavetEnginePool.java | 2 + .../javet/mock/MockCallbackReceiver.java | 1 + .../tutorial/DecimalJavetInNodeJSMode.java | 5 + .../javet/tutorial/DecimalJavetInV8Mode.java | 5 + 50 files changed, 1331 insertions(+), 320 deletions(-) create mode 100644 docker/windows-x86_64/build.Dockerfile create mode 100644 src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueFunctionInvocationHandler.java create mode 100644 src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueObjectInvocationHandler.java create mode 100644 src/main/java/com/caoccao/javet/utils/JavetVirtualObject.java diff --git a/README.rst b/README.rst index a9897e777..3384d5424 100644 --- a/README.rst +++ b/README.rst @@ -52,14 +52,14 @@ Maven com.caoccao.javet javet - 0.9.9 + 0.9.10 com.caoccao.javet javet-macos - 0.9.9 + 0.9.10 Gradle Kotlin DSL @@ -67,16 +67,16 @@ Gradle Kotlin DSL .. code-block:: kotlin - implementation("com.caoccao.javet:javet:0.9.9") // Linux or Windows - implementation("com.caoccao.javet:javet-macos:0.9.9") // Mac OS (x86_64 Only) + implementation("com.caoccao.javet:javet:0.9.10") // Linux or Windows + implementation("com.caoccao.javet:javet-macos:0.9.10") // Mac OS (x86_64 Only) Gradle Groovy DSL ^^^^^^^^^^^^^^^^^ .. code-block:: groovy - implementation 'com.caoccao.javet:javet:0.9.9' // Linux or Windows - implementation 'com.caoccao.javet:javet-macos:0.9.9' // Mac OS (x86_64 Only) + implementation 'com.caoccao.javet:javet:0.9.10' // Linux or Windows + implementation 'com.caoccao.javet:javet-macos:0.9.10' // Mac OS (x86_64 Only) Hello Javet ----------- diff --git a/build.gradle.kts b/build.gradle.kts index 11c9689f5..60e421b08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ repositories { } group = "com.caoccao.javet" -version = "0.9.9" +version = "0.9.10" repositories { mavenCentral() diff --git a/cpp/build-linux.sh b/cpp/build-linux.sh index e2358c1e9..ef2cc8062 100755 --- a/cpp/build-linux.sh +++ b/cpp/build-linux.sh @@ -2,7 +2,7 @@ # Usage for V8: sh build-linux.sh -DV8_DIR=${HOME}/v8 # Usage for Node: sh build-linux.sh -DNODE_DIR=${HOME}/node -JAVET_VERSION=0.9.9 +JAVET_VERSION=0.9.10 rm -rf build mkdir build cd build diff --git a/cpp/build-macos.sh b/cpp/build-macos.sh index 107565ac5..bf7141ad4 100755 --- a/cpp/build-macos.sh +++ b/cpp/build-macos.sh @@ -2,7 +2,7 @@ # Usage for V8: sh build-macos.sh -DV8_DIR=${HOME}/v8 # Usage for Node: sh build-macos.sh -DNODE_DIR=${HOME}/node -JAVET_VERSION=0.9.9 +JAVET_VERSION=0.9.10 rm -rf build mkdir build cd build diff --git a/cpp/build-windows.cmd b/cpp/build-windows.cmd index 6bf45d659..81bda13a0 100644 --- a/cpp/build-windows.cmd +++ b/cpp/build-windows.cmd @@ -1,7 +1,7 @@ @echo off REM Usage for V8: build -DV8_DIR=C:\v8 REM Usage for Node: build -DNODE_DIR=C:\node -SET JAVET_VERSION=0.9.9 +SET JAVET_VERSION=0.9.10 rd /s/q build mkdir build cd build diff --git a/cpp/jni/javet_resource_node.rc b/cpp/jni/javet_resource_node.rc index ce4c6a88b..049116169 100644 --- a/cpp/jni/javet_resource_node.rc +++ b/cpp/jni/javet_resource_node.rc @@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,9,0 - PRODUCTVERSION 0,9,9,0 + FILEVERSION 0,9,10,0 + PRODUCTVERSION 0,9,10,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -79,12 +79,12 @@ BEGIN BEGIN VALUE "CompanyName", "caoccao.com" VALUE "FileDescription", "caoccao.com" - VALUE "FileVersion", "0.9.9.0" - VALUE "InternalName", "libjavet-node-windows-x86_64.v.0.9.9.dll" + VALUE "FileVersion", "0.9.10.0" + VALUE "InternalName", "libjavet-node-windows-x86_64.v.0.9.10.dll" VALUE "LegalCopyright", "Copyright (C) 2021" - VALUE "OriginalFilename", "libjavet-node-windows-x86_64.v.0.9.9.dll" + VALUE "OriginalFilename", "libjavet-node-windows-x86_64.v.0.9.10.dll" VALUE "ProductName", "Javet Windows" - VALUE "ProductVersion", "0.9.9.0" + VALUE "ProductVersion", "0.9.10.0" END END BLOCK "VarFileInfo" diff --git a/cpp/jni/javet_resource_v8.rc b/cpp/jni/javet_resource_v8.rc index bf9ac3a62..365be50b0 100644 --- a/cpp/jni/javet_resource_v8.rc +++ b/cpp/jni/javet_resource_v8.rc @@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,9,0 - PRODUCTVERSION 0,9,9,0 + FILEVERSION 0,9,10,0 + PRODUCTVERSION 0,9,10,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -79,12 +79,12 @@ BEGIN BEGIN VALUE "CompanyName", "caoccao.com" VALUE "FileDescription", "caoccao.com" - VALUE "FileVersion", "0.9.9.0" - VALUE "InternalName", "libjavet-v8-windows-x86_64.v.0.9.9.dll" + VALUE "FileVersion", "0.9.10.0" + VALUE "InternalName", "libjavet-v8-windows-x86_64.v.0.9.10.dll" VALUE "LegalCopyright", "Copyright (C) 2021" - VALUE "OriginalFilename", "libjavet-v8-windows-x86_64.v.0.9.9.dll" + VALUE "OriginalFilename", "libjavet-v8-windows-x86_64.v.0.9.10.dll" VALUE "ProductName", "Javet Windows" - VALUE "ProductVersion", "0.9.9.0" + VALUE "ProductVersion", "0.9.10.0" END END BLOCK "VarFileInfo" diff --git a/docker/windows-x86_64/base.Dockerfile b/docker/windows-x86_64/base.Dockerfile index e8468877c..304b06009 100644 --- a/docker/windows-x86_64/base.Dockerfile +++ b/docker/windows-x86_64/base.Dockerfile @@ -13,21 +13,111 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Usage: docker build -t sjtucaocao/javet-windows:0.9.9 -f docker/windows-x86_64/base.Dockerfile . -# Note: This is experimental and it doesn't work as expected yet. +# Preparation: +# Visual Studio installer validates free disk space and refuses to work with the docker default one. +# Please follow the steps to set free disk space to 120GB. +# 1. Update daemon.json +# "storage-opts": [ +# "dm.basesize=120GB", +# "size=120GB" +# ] +# 2. Restart WSL2 +# 3. Restart docker -# https://hub.docker.com/_/microsoft-dotnet-framework-sdk/ -FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-20H2 +# Usage: docker build -t sjtucaocao/javet-windows:0.9.10 -m 4G -f docker/windows-x86_64/base.Dockerfile . + +# https://hub.docker.com/_/microsoft-windows +FROM mcr.microsoft.com/windows:20H2-amd64 SHELL ["cmd", "/S", "/C"] -RUN curl -SL --output vs_buildtools.exe https://aka.ms/vs/16/release/vs_buildtools.exe -RUN start /w vs_buildtools.exe --quiet --wait --norestart --nocache modify \ - --installPath "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\BuildTools" \ +WORKDIR / + +# Install Python 3 +RUN curl -SL --output python-3.9.6-amd64.exe https://www.python.org/ftp/python/3.9.6/python-3.9.6-amd64.exe +RUN start /w python-3.9.6-amd64.exe /quiet InstallAllUsers=1 PrependPath=0 +RUN del /q python-3.9.6-amd64.exe + +# Install Python 2 +RUN curl -SL --output python-2.7.18.msi https://www.python.org/ftp/python/2.7.18/python-2.7.18.msi +RUN start /w msiexec.exe /i python-2.7.18.msi ALLUSERS=1 ADDLOCAL=ALL /qn +RUN del /q python-2.7.18.msi + +# Install Git for Windows +# https://github.com/git-for-windows/git/wiki/Silent-or-Unattended-Installation +RUN curl -SL --output Git-2.32.0.2-64-bit.exe https://github.com/git-for-windows/git/releases/download/v2.32.0.windows.2/Git-2.32.0.2-64-bit.exe +RUN start /w Git-2.32.0.2-64-bit.exe /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh" +RUN del /q Git-2.32.0.2-64-bit.exe + +# Prepare V8 +RUN mkdir google +WORKDIR /google +RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +WORKDIR /google/depot_tools +RUN git checkout remotes/origin/master +RUN setx /M PATH "C:\google\depot_tools;%PATH%" +ENV DEPOT_TOOLS_WIN_TOOLCHAIN=0 +WORKDIR /google +RUN fetch v8 +WORKDIR /google/v8 +RUN git checkout 9.2.230.21 +WORKDIR /google +RUN gclient sync +RUN echo V8 preparation is completed. + +# Install Visual Studio 2019 Community +# https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-community?view=vs-2019 +# https://docs.microsoft.com/en-us/visualstudio/install/create-an-offline-installation-of-visual-studio?view=vs-2019 +WORKDIR / +RUN curl -SL --output vs_community.exe https://aka.ms/vs/16/release/vs_community.exe +RUN echo Installing Visual Studio 2019 Community +RUN start /w vs_community.exe \ + --wait --quiet --norestart --nocache --includeRecommended --includeOptional \ + --installPath "%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community" \ + --add Microsoft.VisualStudio.Workload.NativeDesktop \ + --add Microsoft.VisualStudio.Workload.NativeCrossPlat \ --remove Microsoft.VisualStudio.Component.Windows10SDK.10240 \ --remove Microsoft.VisualStudio.Component.Windows10SDK.10586 \ - --remove Microsoft.VisualStudio.Component.Windows81SDK \ || IF "%ERRORLEVEL%"=="3010" EXIT 0 -RUN del /q vs_buildtools.exe -ENTRYPOINT ["C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"] +# Install Windows SDK 10.0.19041.x +RUN curl -SL --output winsdksetup.exe https://go.microsoft.com/fwlink/p/?linkid=2120843 +RUN start /w winsdksetup.exe /norestart /quiet /ceip off /features + +RUN del /q vs_community.exe +RUN del /q winsdksetup.exe + +# Build V8 +WORKDIR /google/v8 +RUN setx /M PATH "C:\Python27;C:\Python27\Scripts;%PATH%" +RUN python tools/dev/v8gen.py x64.release -vv -- v8_monolithic=true v8_use_external_startup_data=false is_component_build=false v8_enable_i18n_support=false v8_enable_pointer_compression=false v8_static_library=true symbol_level=0 use_custom_libcxx=false +RUN ninja -C out.gn/x64.release v8_monolith || EXIT 0 +COPY ./scripts/python/patch_v8_build.py . +RUN ["C:\\Program Files\\Python39\\python.exe", "C:\\google\\v8\\patch_v8_build.py", "-p", "C:\\google\\v8\\"] +RUN ninja -C out.gn/x64.release v8_monolith +RUN del patch_v8_build.py +RUN echo V8 build is completed. + +# Prepare Node.js +WORKDIR / +RUN powershell -ExecutionPolicy Bypass -c "iex(New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')" +RUN choco install -y nasm +RUN git clone https://github.com/nodejs/node.git +WORKDIR /node +RUN git checkout v14.17.4 +RUN echo Node.js preparation is completed. + +# Build Node.js +RUN vcbuild.bat static without-intl +RUN echo Node.js build is completed. + +# Prepare Javet Build Environment +RUN choco install -y openjdk8 +RUN setx /M PATH "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin;%PATH%" +RUN setx /M PATH "C:\Program Files\Git\usr\bin;%PATH%" + +# Shrink +WORKDIR / +RUN rd /s /q "C:\Users\ContainerAdministrator\AppData\Local\Temp" + +# Completed +RUN echo Javet build base image is completed. diff --git a/docker/windows-x86_64/build.Dockerfile b/docker/windows-x86_64/build.Dockerfile new file mode 100644 index 000000000..3a5f4eb70 --- /dev/null +++ b/docker/windows-x86_64/build.Dockerfile @@ -0,0 +1,43 @@ +# Copyright (c) 2021 caoccao.com Sam Cao +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Usage: docker build -t javet:local -f docker/windows-x86_64/build.Dockerfile . + +FROM sjtucaocao/javet-windows:0.9.10 + +SHELL ["cmd", "/S", "/C"] +WORKDIR / + +# Copy Javet +RUN mkdir Javet +WORKDIR /Javet +COPY . . + +# Build JNI +WORKDIR /Javet/cpp +RUN build-windows.cmd -DV8_DIR="C:\google\v8" +RUN build-windows.cmd -DNODE_DIR="C:\node" + +# Build Jar +WORKDIR /Javet +RUN touch src/main/resources/libjavet-v8* +RUN gradlew build test --rerun-tasks +RUN touch src/main/resources/libjavet-node* +RUN gradlew test --rerun-tasks + +VOLUME C:\\output + +# Completed +RUN echo Javet build is completed. diff --git a/docs/development/build.rst b/docs/development/build.rst index 20229c84a..5d47f1c0d 100644 --- a/docs/development/build.rst +++ b/docs/development/build.rst @@ -10,7 +10,7 @@ It's quite hard for developers to build Javet successfully for various reasons. Here are 3 ways of building Javet. -1. `Build Javet with Docker `_ (Linux) +1. `Build Javet with Docker `_ (Linux and Windows) 2. `Build Javet with Pre-built Binaries `_ (Linux, Mac OS and Windows) 3. `Build Javet from Scratch `_ (Linux, Mac OS and Windows) diff --git a/docs/development/build_javet_with_docker.rst b/docs/development/build_javet_with_docker.rst index f17221f03..138030a6a 100644 --- a/docs/development/build_javet_with_docker.rst +++ b/docs/development/build_javet_with_docker.rst @@ -2,9 +2,9 @@ Build Javet with Docker ======================= -For now, the Docker build only supports building Javet on Linux. As Docker supports Linux and Windows with WSL2, Javet for Linux can also be built on Windows. +The Docker build supports building Javet for Linux and Windows. As Docker supports Linux and Windows with WSL2, Javet for Linux can also be built on Windows. -Regarding Docker build for Mac OS or Windows, contributors are welcome if you are interested. Or, you will have to wait for a long while. +Regarding Docker build for Mac OS, contributors are welcome if you are interested. Or, you will have to wait for a long while. Build Environment ================= @@ -27,10 +27,10 @@ Windows Environment Docker Hub and Github --------------------- -Please make sure the network connection to the Docker Hub and Github is up and running. The Docker repository of the Javet images are available at https://hub.docker.com/repository/docker/sjtucaocao/javet. +Please make sure the network connection to the Docker Hub and Github is up and running. The Docker repository of the Javet images are available at https://hub.docker.com/repository/docker/sjtucaocao. -Build Javet on Linux -==================== +Build Javet for Linux on Linux or Windows +========================================= 1. Clone Javet. 2. Navigate to the root directory of the Javet repository. @@ -39,4 +39,28 @@ Build Javet on Linux * Docker will pull the corresponding image (~10GB) from Docker Hub. * The actual build takes few minutes including pulling dependent libraries from Maven Central, building and testing. +Build Javet for Windows on Windows +================================== + +1. Update daemon.json + +.. code-block:: json + + "storage-opts": [ + "dm.basesize=120GB", + "size=120GB" + ] + +2. Restart WSL2 +3. Restart docker +4. Clone Javet. +5. Navigate to the root directory of the Javet repository. +6. Execute ``docker build -t sjtucaocao/javet-windows:x.x.x -m 4G -f docker/windows-x86_64/base.Dockerfile .`` (Be careful, please include the last ``.``). +7. Execute ``docker build -f docker/windows-x86_64/build.Dockerfile .`` (Be careful, please include the last ``.``). + +Note: + +* The base image is so large (60+GB) that it's not efficient to push the base image to docker hub. Of course, without the base image at docker hub, it's not wise to enable the github workflow for Windows build. +* Building the base image takes many hours and may experience intermittent errors. + [`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/development/build_javet_with_pre_built_binaries.rst b/docs/development/build_javet_with_pre_built_binaries.rst index 294741c03..3debd6f57 100644 --- a/docs/development/build_javet_with_pre_built_binaries.rst +++ b/docs/development/build_javet_with_pre_built_binaries.rst @@ -38,6 +38,8 @@ Download Pre-built Node.js and V8 I have prepared pre-built Linux and Windows version of Node.js ``v14.17.4`` and V8 ``v9.2.230.21``. Please download the zipped headers and binaries from this `drive `_ and unzip them to local folders respectively. +Note: As the docker builds are available, I'll stop publishing pre-built binaries. If you really need them, please contact the maintainer wisely. + Build Javet JNI Library ======================= diff --git a/docs/development/index.rst b/docs/development/index.rst index 3e5e75144..8da39dc1c 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -5,7 +5,7 @@ Development * `Development Tools `_ * `Build `_ - * `Build Javet with Docker `_ (Linux) + * `Build Javet with Docker `_ (Linux and Windows) * `Build Javet with Pre-built Binaries `_ (Linux, Mac OS and Windows) * `Build Javet from Scratch `_ (Linux, Mac OS and Windows) diff --git a/docs/reference/error_codes.rst b/docs/reference/error_codes.rst index cc4f71a25..e31300b2e 100644 --- a/docs/reference/error_codes.rst +++ b/docs/reference/error_codes.rst @@ -40,6 +40,8 @@ Code Type Name Format 501 Converter ConverterFailure Failed to convert values with error message ${message} 502 Converter ConverterCircularStructure Circular structure is detected with max depth ${maxDepth} reached 601 Module ModuleNameEmpty Module name is empty +602 Module ModuleNotFound Module ${moduleName} is not found +603 Module ModulePermissionDenied Denied access to module ${moduleName} 701 Lock LockAcquisitionFailure Failed to acquire the lock 702 Lock LockReleaseFailure Failed to release the lock 703 Lock LockConflictThreadIdMismatch Runtime lock conflict is detected with locked thread ID ${lockedThreadID} and current thread ID ${currentThreadID} diff --git a/docs/release_notes.rst b/docs/release_notes.rst index 34de260ce..6c7b8c977 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -2,6 +2,16 @@ Release Notes ============= +0.9.10 +------ + +* Added ``JavetVirtualObject`` +* Updated ``JavetUniversalProxyHandler`` to allow passing ``V8Value`` +* Updated ``JavetUniversalProxyHandler`` to allow passing ``V8ValueFunction`` as anonymous function +* Updated ``JavetUniversalProxyHandler`` to allow passing ``V8ValueObject`` as anonymous object +* Added ``isClosed()`` to ``IJavetClosable`` +* Added error code 602 and 603 + 0.9.9 ----- diff --git a/docs/tutorial/installation.rst b/docs/tutorial/installation.rst index 33b873fe6..cd4de859d 100644 --- a/docs/tutorial/installation.rst +++ b/docs/tutorial/installation.rst @@ -14,14 +14,14 @@ Maven com.caoccao.javet javet - 0.9.9 + 0.9.10 - + com.caoccao.javet javet-macos - 0.9.9 + 0.9.10 Gradle Kotlin DSL @@ -29,15 +29,16 @@ Gradle Kotlin DSL .. code-block:: kotlin - implementation("com.caoccao.javet:javet:0.9.9") // Linux or Windows - implementation("com.caoccao.javet:javet-macos:0.9.9") // Mac OS + implementation("com.caoccao.javet:javet:0.9.10") // Linux or Windows + implementation("com.caoccao.javet:javet-macos:0.9.10") // Mac OS (x86_64 Only) Gradle Groovy DSL ----------------- .. code-block:: groovy - implementation 'com.caoccao.javet:javet:0.9.9' + implementation 'com.caoccao.javet:javet:0.9.10' // Linux or Windows + implementation 'com.caoccao.javet:javet-macos:0.9.10' // Mac OS (x86_64 Only) OS Compatibility ================ @@ -52,7 +53,7 @@ Ubuntu 20.04 Yes Ubuntu 18.04 Yes (`Private Build `_) Ubuntu 16.04 Yes (`Private Build `_) Other Linux Distributions Not Tested -MacOS x86_64 Experimental (`Private Build `_) +MacOS x86_64 Yes MacOS arm64 No (`Can Javet Support Mac? <../faq/can_javet_support_mac.rst>`_) =========================== ======================================================================================================================= diff --git a/docs/tutorial/object_converter.rst b/docs/tutorial/object_converter.rst index 605afd13e..2c413a4a6 100644 --- a/docs/tutorial/object_converter.rst +++ b/docs/tutorial/object_converter.rst @@ -282,6 +282,135 @@ Sometimes an interface or annotation class can be injected for enabling Java ref v8Runtime.getGlobalObject().delete("IJavetClosable"); v8Runtime.lowMemoryNotification(); +Dynamic: Anonymous Function +--------------------------- + +This feature is quite special as it allows implementing Java interfaces in JavaScript via anonymous functions, also known as lambda expressions. + +1. Define a simple interface ``IStringJoiner`` for joining two strings. + +.. code-block:: java + + interface IStringJoiner extends AutoCloseable { + String join(String a, String b); + } + +2. Define a simple class ``StringJoiner`` which holds the interface ``IStringJoiner``. + +.. code-block:: java + + public class StringJoiner implements AutoCloseable { + private IStringJoiner joiner; + + public StringJoiner() { + joiner = null; + } + + @Override + public void close() throws Exception { + if (joiner != null) { + joiner.close(); + joiner = null; + } + } + + public IStringJoiner getJoiner() { + return joiner; + } + + public void setJoiner(IStringJoiner joiner) { + this.joiner = joiner; + } + } + +3. Inject the implementation from JavaScript. + +.. code-block:: java + + try (StringJoiner stringJoiner = new StringJoiner()) { + v8Runtime.getGlobalObject().set("stringJoiner", stringJoiner); + v8Runtime.getExecutor("stringJoiner.setJoiner((a, b) => a + ',' + b);").executeVoid(); + IStringJoiner joiner = stringJoiner.getJoiner(); + assertEquals("a,b", joiner.join("a", "b")); + assertEquals("a,b,c", joiner.join(joiner.join("a", "b"), "c")); + v8Runtime.getGlobalObject().delete("stringJoiner"); + } + v8Runtime.lowMemoryNotification(); + +VoilĂ ! It works. + +Note: The JavaScript implementation is backed up by ``V8ValueFunction`` which is an orphan object. After its internal ``V8Runtime`` is closed, it will no longer callable. It's recommended to have the interface implement ``AutoClosable`` as the sample shows so that the orphan ``V8ValueFunction`` can be recycled explicitly. If you don't own the interface, Javet will force the recycle of the orphan ``V8ValueFunction`` when the ``V8Runtime`` is being closed. Be careful, if you keep the application running for long while without recycling them in time, ``OutOfMemoryError`` may occur. + +Dynamic: Anonymous Object +------------------------- + +This feature is similar to the dynamic anonymous function, but is an enhanced version because it allows implementing all methods exposed by the Java interface. + +1. Define a simple interface ``IStringUtils`` for joining two strings. + +.. code-block:: java + + interface IStringUtils extends AutoCloseable { + String hello(); + String join(String separator, String... strings); + List split(String separator, String string); + } + +2. Define a simple class ``StringUtils`` which holds the interface ``IStringUtils``. + +.. code-block:: java + + public class StringUtils implements AutoCloseable { + private IStringUtils utils; + + public StringUtils() { + utils = null; + } + + @Override + public void close() throws Exception { + if (utils != null) { + utils.close(); + utils = null; + } + } + + public IStringUtils getUtils() { + return utils; + } + + public void setUtils(IStringUtils utils) { + this.utils = utils; + } + } + +3. Inject the implementation from JavaScript. + +.. code-block:: java + + try (StringUtils stringUtils = new StringUtils()) { + v8Runtime.getGlobalObject().set("stringUtils", stringUtils); + v8Runtime.getExecutor( + "stringUtils.setUtils({\n" + + " hello: () => 'hello',\n" + + " join: (separator, ...strings) => [...strings].join(separator),\n" + + " split: (separator, str) => str.split(separator),\n" + + "});" + ).executeVoid(); + IStringUtils utils = stringUtils.getUtils(); + assertEquals("hello", utils.hello()); + assertEquals("a,b,c", utils.join(",", "a", "b", "c")); + assertArrayEquals( + new String[]{"a", "b", "c"}, + utils.split(",", "a,b,c").toArray(new String[0])); + v8Runtime.getGlobalObject().delete("stringUtils"); + } + v8Runtime.lowMemoryNotification(); + +VoilĂ  aussi! It works again. + +Note: The JavaScript implementation is backed up by ``V8ValueObject`` which is an orphan object. After its internal ``V8Runtime`` is closed, it will no longer callable. It's recommended to have the interface implement ``AutoClosable`` as the sample shows so that the orphan ``V8ValueObject`` can be recycled explicitly. If you don't own the interface, Javet will force the recycle of the orphan ``V8ValueObject`` when the ``V8Runtime`` is being closed. Be careful, if you keep the application running for long while without recycling them in time, ``OutOfMemoryError`` may occur. + Features -------- @@ -289,6 +418,7 @@ Features * Getters and setters (``get``, ``is``, ``set`` and ``put``) are smartly handled. * Overloaded methods and varargs methods are identified well. * Primitive types, Set, Map, List, Array are not handled. Map is special because it can be enabled. +* Java interfaces can be implemented by anonymous functions in JavaScript. How does JavetProxyConverter Work? ---------------------------------- diff --git a/pom.xml b/pom.xml index e42dab25d..d6e3944f4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.caoccao.javet javet - 0.9.9 + 0.9.10 javet Javet is Java + V8 (JAVa + V + EighT). It is a way of embedding V8 in Java. https://github.com/caoccao/Javet @@ -28,7 +28,7 @@ scm:git:git://github.com/caoccao/Javet.git scm:git:git@github.com:caoccao/caoccao.git https://github.com/caoccao/Javet - javet-0.9.9 + javet-0.9.10 diff --git a/scripts/node/javet-rebuild/rebuild.cmd b/scripts/node/javet-rebuild/rebuild.cmd index afdefeb73..76a650344 100644 --- a/scripts/node/javet-rebuild/rebuild.cmd +++ b/scripts/node/javet-rebuild/rebuild.cmd @@ -1,5 +1,5 @@ @echo off -SET NODE_LIB_FILE="..\..\..\..\..\..\build\libs\libjavet-node-windows-x86_64.v.0.9.9.lib" +SET NODE_LIB_FILE="..\..\..\..\..\..\build\libs\libjavet-node-windows-x86_64.v.0.9.10.lib" cd %NODE_MODULE_ROOT% call node-gyp clean call node-gyp configure --module_name=%NODE_MODULE_NAME% --module_path=%NODE_MODULE_PATH% --node_lib_file=%NODE_LIB_FILE% diff --git a/scripts/node/javet-rebuild/rebuild.sh b/scripts/node/javet-rebuild/rebuild.sh index 440a95093..9d9e9d0b9 100755 --- a/scripts/node/javet-rebuild/rebuild.sh +++ b/scripts/node/javet-rebuild/rebuild.sh @@ -1 +1 @@ -patchelf --add-needed libjavet-node-linux-x86_64.v.0.9.9.so ${NODE_MODULE_FILE} +patchelf --add-needed libjavet-node-linux-x86_64.v.0.9.10.so ${NODE_MODULE_FILE} diff --git a/scripts/python/change_javet_version.py b/scripts/python/change_javet_version.py index 9d30443ab..5501d7c68 100644 --- a/scripts/python/change_javet_version.py +++ b/scripts/python/change_javet_version.py @@ -35,8 +35,7 @@ def update(self): self._update( 'README.rst', '\n', re.compile(r'^ (?P\d+\.\d+\.\d+)$'), - re.compile(r'javet:(?P\d+\.\d+\.\d+)"'), - re.compile(r'javet:(?P\d+\.\d+\.\d+)\''), + re.compile(r'javet[\-\w]*:(?P\d+\.\d+\.\d+)["\']{1}'), re.compile(r'version: \'(?P\d+\.\d+\.\d+)\'')) self._update( 'build.gradle.kts', '\n', @@ -44,18 +43,20 @@ def update(self): self._update( 'docs/tutorial/installation.rst', '\n', re.compile(r'^ (?P\d+\.\d+\.\d+)$'), - re.compile(r'javet:(?P\d+\.\d+\.\d+)"'), - re.compile(r'javet:(?P\d+\.\d+\.\d+)\''), + re.compile(r'javet[\-\w]*:(?P\d+\.\d+\.\d+)["\']{1}'), re.compile(r'version: \'(?P\d+\.\d+\.\d+)\'')) self._update( 'pom.xml', '\n', re.compile(r'^ (?P\d+\.\d+\.\d+)$'), re.compile(r'^ javet-(?P\d+\.\d+\.\d+)$')) self._update( - 'cpp/build.cmd', '\r\n', + 'cpp/build-linux.sh', '\n', re.compile(r'JAVET_VERSION=(?P\d+\.\d+\.\d+)$')) self._update( - 'cpp/build.sh', '\n', + 'cpp/build-macos.sh', '\n', + re.compile(r'JAVET_VERSION=(?P\d+\.\d+\.\d+)$')) + self._update( + 'cpp/build-windows.cmd', '\r\n', re.compile(r'JAVET_VERSION=(?P\d+\.\d+\.\d+)$')) self._update( 'src/main/java/com/caoccao/javet/interop/JavetLibLoader.java', '\n', @@ -109,7 +110,7 @@ def _update(self, relative_file_path: str, line_separator: str, *patterns: list) logging.info(' Updated.') def main(): - change_javet_version = ChangeJavetVersion('0.9.9') + change_javet_version = ChangeJavetVersion('0.9.10') change_javet_version.update() return 0 diff --git a/src/main/java/com/caoccao/javet/exceptions/JavetError.java b/src/main/java/com/caoccao/javet/exceptions/JavetError.java index fac7e2c7f..7ce51b189 100644 --- a/src/main/java/com/caoccao/javet/exceptions/JavetError.java +++ b/src/main/java/com/caoccao/javet/exceptions/JavetError.java @@ -91,6 +91,10 @@ public class JavetError { public static final JavetError ModuleNameEmpty = new JavetError( 601, JavetErrorType.Module, "Module name is empty"); + public static final JavetError ModuleNotFound = new JavetError( + 602, JavetErrorType.Module, "Module ${moduleName} is not found"); + public static final JavetError ModulePermissionDenied = new JavetError( + 603, JavetErrorType.Module, "Denied access to module ${moduleName}"); public static final JavetError LockAcquisitionFailure = new JavetError( 701, JavetErrorType.Lock, "Failed to acquire the lock"); diff --git a/src/main/java/com/caoccao/javet/interfaces/IJavetClosable.java b/src/main/java/com/caoccao/javet/interfaces/IJavetClosable.java index 75457bb2b..f4dc0e2aa 100644 --- a/src/main/java/com/caoccao/javet/interfaces/IJavetClosable.java +++ b/src/main/java/com/caoccao/javet/interfaces/IJavetClosable.java @@ -26,4 +26,12 @@ */ public interface IJavetClosable extends AutoCloseable { void close() throws JavetException; + + /** + * Is closed. + * + * @return the boolean + * @since 0.9.10 + */ + boolean isClosed(); } diff --git a/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java b/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java index e7694a9c2..75b1f9293 100644 --- a/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java +++ b/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java @@ -33,7 +33,7 @@ import java.util.Objects; public final class JavetLibLoader { - static final String LIB_VERSION = "0.9.9"; + static final String LIB_VERSION = "0.9.10"; private static final int BUFFER_LENGTH = 4096; private static final String CHMOD = "chmod"; private static final String LIB_FILE_EXTENSION_LINUX = "so"; diff --git a/src/main/java/com/caoccao/javet/interop/V8Locker.java b/src/main/java/com/caoccao/javet/interop/V8Locker.java index 31ea6f1bf..755b5317f 100644 --- a/src/main/java/com/caoccao/javet/interop/V8Locker.java +++ b/src/main/java/com/caoccao/javet/interop/V8Locker.java @@ -27,18 +27,21 @@ /** * The type V8 locker. * It's designed for performance sensitive scenarios. + * @since 0.7.3 */ public final class V8Locker implements IJavetClosable { private final long threadId; private final IV8Native v8Native; private final V8Runtime v8Runtime; + private boolean locked; /** * Instantiates a new V8 locker. * * @param v8Runtime the V8 runtime - * @param v8Native the v 8 native + * @param v8Native the V8 native * @throws JavetException the javet exception + * @since 0.7.3 */ V8Locker(V8Runtime v8Runtime, IV8Native v8Native) throws JavetException { Objects.requireNonNull(v8Runtime); @@ -48,6 +51,7 @@ public final class V8Locker implements IJavetClosable { if (!v8Native.lockV8Runtime(v8Runtime.getHandle())) { throw new JavetException(JavetError.LockAcquisitionFailure); } + locked = true; } @Override @@ -61,5 +65,21 @@ public void close() throws JavetException { if (!v8Native.unlockV8Runtime(v8Runtime.getHandle())) { throw new JavetException(JavetError.LockReleaseFailure); } + locked = false; + } + + @Override + public boolean isClosed() { + return !locked; + } + + /** + * Is locked. + * + * @return the boolean + * @since 0.9.10 + */ + public boolean isLocked() { + return locked; } } diff --git a/src/main/java/com/caoccao/javet/interop/V8Runtime.java b/src/main/java/com/caoccao/javet/interop/V8Runtime.java index a231aa63d..bbd7c0e74 100644 --- a/src/main/java/com/caoccao/javet/interop/V8Runtime.java +++ b/src/main/java/com/caoccao/javet/interop/V8Runtime.java @@ -158,7 +158,7 @@ public void close() throws JavetException { } public void close(boolean forceClose) throws JavetException { - if (handle != INVALID_HANDLE && forceClose) { + if (!isClosed() && forceClose) { removeAllReferences(); v8Host.closeV8Runtime(this); handle = INVALID_HANDLE; @@ -541,7 +541,7 @@ public boolean hasOwnProperty(IV8ValueObject iV8ValueObject, V8Value key) throws * @param deadlineInMillis the deadline in millis */ public void idleNotificationDeadline(long deadlineInMillis) { - if (handle != INVALID_HANDLE && deadlineInMillis > 0) { + if (!isClosed() && deadlineInMillis > 0) { v8Native.idleNotificationDeadline(handle, deadlineInMillis); } } @@ -576,6 +576,11 @@ public T invoke( handle, iV8ValueObject.getHandle(), iV8ValueObject.getType().getId(), functionName, returnResult, v8Values)); } + @Override + public boolean isClosed() { + return handle == INVALID_HANDLE; + } + public boolean isDead() { return v8Native.isDead(handle); } @@ -600,7 +605,7 @@ public boolean isWeak(IV8ValueReference iV8ValueReference) { * Send low memory notification to current V8 isolate. */ public void lowMemoryNotification() { - if (handle != INVALID_HANDLE) { + if (!isClosed()) { v8Native.lowMemoryNotification(handle); } } @@ -735,7 +740,7 @@ protected void removeCallbackContexts() { } public void removeJNIGlobalRef(long handle) { - if (handle != INVALID_HANDLE) { + if (!isClosed()) { v8Native.removeJNIGlobalRef(handle); } } diff --git a/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java b/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java index 425cf9bbe..cc7b0f0c8 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java @@ -97,6 +97,11 @@ public boolean isActive() { return active; } + @Override + public boolean isClosed() { + return v8Runtime == null || v8Runtime.isClosed(); + } + @Override public void resetContext() throws JavetException { v8Runtime.resetContext(); diff --git a/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java index 774a79215..cc56f5d62 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java @@ -29,19 +29,74 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +/** + * The type Javet engine guard. + * + * @since 0.7.2 + */ public class JavetEngineGuard implements IJavetEngineGuard { + /** + * The constant IS_IN_DEBUG_MODE. + * + * @since 0.8.9 + */ protected static final boolean IS_IN_DEBUG_MODE = ManagementFactory.getRuntimeMXBean(). getInputArguments().toString().indexOf("-agentlib:jdwp") > 0; + /** + * The Closed. + * + * @since 0.9.10 + */ + protected boolean closed; + /** + * The Future. + * + * @since 0.8.10 + */ protected Future future; + /** + * The Javet engine. + * + * @since 0.8.10 + */ protected IJavetEngine iJavetEngine; + /** + * The Quitting. + * + * @since 0.8.10 + */ protected volatile boolean quitting; + /** + * The Skip in debug mode. + * + * @since 0.8.9 + */ protected boolean skipInDebugMode; + /** + * The Timeout millis. + * + * @since 0.8.9 + */ protected long timeoutMillis; + /** + * The V8 runtime. + * + * @since 0.7.2 + */ protected V8Runtime v8Runtime; + /** + * Instantiates a new Javet engine guard. + * + * @param iJavetEngine the javet engine + * @param v8Runtime the V8 runtime + * @param timeoutMills the timeout mills + * @since 0.7.2 + */ public JavetEngineGuard(IJavetEngine iJavetEngine, V8Runtime v8Runtime, long timeoutMills) { Objects.requireNonNull(iJavetEngine); + closed = false; this.iJavetEngine = iJavetEngine; quitting = false; skipInDebugMode = true; @@ -61,6 +116,7 @@ public void close() throws JavetException { if (!future.isDone() && !future.isCancelled()) { future.cancel(true); } + closed = true; } @Override @@ -78,10 +134,27 @@ public long getTimeoutMillis() { return timeoutMillis; } + /** + * Gets utc now. + * + * @return the utc now + * @since 0.9.1 + */ protected ZonedDateTime getUTCNow() { return JavetDateTimeUtils.getUTCNow(); } + @Override + public boolean isClosed() { + return closed || v8Runtime == null || v8Runtime.isClosed(); + } + + /** + * Is quitting boolean. + * + * @return the boolean + * @since 0.7.2 + */ public boolean isQuitting() { return quitting; } diff --git a/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java b/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java index 36373f696..8e1648ece 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java @@ -30,20 +30,77 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +/** + * The type Javet engine pool. + * + * @param the type parameter + * @since 0.8.0 + */ public class JavetEnginePool implements IJavetEnginePool, Runnable { + /** + * The constant JAVET_DAEMON_THREAD_NAME. + * + * @since 0.8.10 + */ protected static final String JAVET_DAEMON_THREAD_NAME = "Javet Daemon"; + /** + * The Active engine list. + * + * @since 0.9.1 + */ protected final ConcurrentLinkedQueue> activeEngineList; + /** + * The External lock. + * + * @since 0.8.10 + */ protected final Object externalLock; + /** + * The Idle engine list. + * + * @since 0.9.1 + */ protected final ConcurrentLinkedQueue> idleEngineList; + /** + * The Active. + * + * @since 0.7.0 + */ protected volatile boolean active; + /** + * The Config. + * + * @since 0.7.0 + */ protected JavetEngineConfig config; + /** + * The Daemon thread. + * + * @since 0.7.0 + */ protected Thread daemonThread; + /** + * The Quitting. + * + * @since 0.7.0 + */ protected volatile boolean quitting; + /** + * Instantiates a new Javet engine pool. + * + * @since 0.7.0 + */ public JavetEnginePool() { this(new JavetEngineConfig()); } + /** + * Instantiates a new Javet engine pool. + * + * @param config the config + * @since 0.7.0 + */ public JavetEnginePool(JavetEngineConfig config) { Objects.requireNonNull(config); this.config = config; @@ -60,6 +117,13 @@ public void close() throws JavetException { stopDaemon(); } + /** + * Create engine javet engine. + * + * @return the javet engine + * @throws JavetException the javet exception + * @since 0.7.0 + */ protected JavetEngine createEngine() throws JavetException { V8Host v8Host = V8Host.getInstance(config.getJSRuntimeType()); @SuppressWarnings("ConstantConditions") @@ -112,6 +176,12 @@ public int getIdleEngineCount() { return idleEngineList.size(); } + /** + * Gets utc now. + * + * @return the utc now + * @since 0.7.0 + */ protected ZonedDateTime getUTCNow() { return JavetDateTimeUtils.getUTCNow(); } @@ -121,6 +191,11 @@ public boolean isActive() { return active; } + @Override + public boolean isClosed() { + return !active; + } + @Override public boolean isQuitting() { return quitting; @@ -224,6 +299,11 @@ public void run() { logger.debug("JavetEnginePool.run() ends."); } + /** + * Start daemon. + * + * @since 0.7.0 + */ protected void startDaemon() { IJavetLogger logger = config.getJavetLogger(); logger.debug("JavetEnginePool.startDaemon() begins."); @@ -239,6 +319,11 @@ protected void startDaemon() { logger.debug("JavetEnginePool.startDaemon() ends."); } + /** + * Stop daemon. + * + * @since 0.7.0 + */ protected void stopDaemon() { IJavetLogger logger = config.getJavetLogger(); logger.debug("JavetEnginePool.stopDaemon() begins."); diff --git a/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueFunctionInvocationHandler.java b/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueFunctionInvocationHandler.java new file mode 100644 index 000000000..b9832c5f0 --- /dev/null +++ b/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueFunctionInvocationHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021. caoccao.com Sam Cao + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.caoccao.javet.interop.proxy; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interfaces.IJavetClosable; +import com.caoccao.javet.utils.JavetResourceUtils; +import com.caoccao.javet.values.reference.V8ValueFunction; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * The type Dynamic proxy V8 value function invocation handler. + */ +public class DynamicProxyV8ValueFunctionInvocationHandler implements InvocationHandler, IJavetClosable { + private static final String METHOD_NAME_CLOSE = "close"; + private V8ValueFunction v8ValueFunction; + + /** + * Instantiates a new Dynamic proxy V8 value function invocation handler. + * + * @param v8ValueFunction the V8 value function + * @since 0.9.10 + */ + public DynamicProxyV8ValueFunctionInvocationHandler(V8ValueFunction v8ValueFunction) { + this.v8ValueFunction = v8ValueFunction; + } + + @Override + public void close() throws JavetException { + JavetResourceUtils.safeClose(v8ValueFunction); + v8ValueFunction = null; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result = null; + if (args == null) { + args = new Object[0]; + } + if (method.getName().equals(METHOD_NAME_CLOSE) && args.length == 0) { + close(); + } else if (v8ValueFunction != null && !v8ValueFunction.isClosed()) { + result = v8ValueFunction.callObject(null, args); + } + return result; + } + + @Override + public boolean isClosed() { + return v8ValueFunction == null || v8ValueFunction.isClosed(); + } +} diff --git a/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueObjectInvocationHandler.java b/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueObjectInvocationHandler.java new file mode 100644 index 000000000..2b320214b --- /dev/null +++ b/src/main/java/com/caoccao/javet/interop/proxy/DynamicProxyV8ValueObjectInvocationHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021. caoccao.com Sam Cao + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.caoccao.javet.interop.proxy; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interfaces.IJavetClosable; +import com.caoccao.javet.utils.JavetResourceUtils; +import com.caoccao.javet.values.reference.V8ValueObject; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * The type Dynamic proxy V8 value object invocation handler. + */ +public class DynamicProxyV8ValueObjectInvocationHandler implements InvocationHandler, IJavetClosable { + private static final String METHOD_NAME_CLOSE = "close"; + private V8ValueObject v8ValueObject; + + /** + * Instantiates a new Dynamic proxy V8 value object invocation handler. + * + * @param v8ValueObject the V8 value object + * @since 0.9.10 + */ + public DynamicProxyV8ValueObjectInvocationHandler(V8ValueObject v8ValueObject) { + this.v8ValueObject = v8ValueObject; + } + + @Override + public void close() throws JavetException { + JavetResourceUtils.safeClose(v8ValueObject); + v8ValueObject = null; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result = null; + if (args == null) { + args = new Object[0]; + } + if (method.getName().equals(METHOD_NAME_CLOSE) && args.length == 0) { + close(); + } else if (v8ValueObject != null && !v8ValueObject.isClosed()) { + result = v8ValueObject.invokeObject(method.getName(), args); + } + return result; + } + + @Override + public boolean isClosed() { + return v8ValueObject == null || v8ValueObject.isClosed(); + } +} diff --git a/src/main/java/com/caoccao/javet/interop/proxy/JavetUniversalProxyHandler.java b/src/main/java/com/caoccao/javet/interop/proxy/JavetUniversalProxyHandler.java index 9a510ea14..174aa5cbd 100644 --- a/src/main/java/com/caoccao/javet/interop/proxy/JavetUniversalProxyHandler.java +++ b/src/main/java/com/caoccao/javet/interop/proxy/JavetUniversalProxyHandler.java @@ -23,13 +23,14 @@ import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.interop.callback.JavetCallbackContext; -import com.caoccao.javet.utils.JavetPrimitiveUtils; -import com.caoccao.javet.utils.JavetReflectionUtils; -import com.caoccao.javet.utils.SimpleMap; +import com.caoccao.javet.utils.*; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.primitive.V8ValueBoolean; import com.caoccao.javet.values.primitive.V8ValueString; import com.caoccao.javet.values.reference.V8ValueArray; +import com.caoccao.javet.values.reference.V8ValueFunction; +import com.caoccao.javet.values.reference.V8ValueObject; +import com.caoccao.javet.values.reference.V8ValueProxy; import java.lang.reflect.*; import java.util.*; @@ -75,6 +76,30 @@ public class JavetUniversalProxyHandler extends BaseJavetProxyHandler { * @since 0.9.6 */ protected static final String[] SETTER_PREFIX_ARRAY = new String[]{"set", "put"}; + /** + * The constant V8_VALUE_CLASS. + * + * @since 0.9.10 + */ + protected static final Class V8_VALUE_CLASS = V8Value.class; + /** + * The constant V8_VALUE_FUNCTION_CLASS. + * + * @since 0.9.10 + */ + protected static final Class V8_VALUE_FUNCTION_CLASS = V8ValueFunction.class; + /** + * The constant V8_VALUE_OBJECT_CLASS. + * + * @since 0.9.10 + */ + protected static final Class V8_VALUE_OBJECT_CLASS = V8ValueObject.class; + /** + * The constant V8_VALUE_PROXY_CLASS. + * + * @since 0.9.10 + */ + protected static final Class V8_VALUE_PROXY_CLASS = V8ValueProxy.class; /** * The Class mode. * @@ -162,85 +187,43 @@ public JavetUniversalProxyHandler(V8Runtime v8Runtime, T targetObject) { } /** - * Calculate score double. + * Execute. * - * @param executable the executable - * @param objects the objects - * @return the score - * @since 0.9.8 + * @param the type parameter + * @param v8Runtime the V8 runtime + * @param targetObject the target object + * @param executables the executables + * @param javetVirtualObjects the javet virtual objects + * @return the object + * @throws Throwable the throwable + * @since 0.9.10 */ - protected static double calculateScore(Executable executable, Object... objects) { - // Max score is 1. Min score is 0. - final int parameterCount = executable.getParameterCount(); - Class[] parameterTypes = executable.getParameterTypes(); - boolean isMethodVarArgs = executable.isVarArgs(); - double score = 0; - final int length = objects.length; - if (length == 0) { - if (isMethodVarArgs) { - if (parameterCount == 1) { - score = 0.99; - } - } else { - if (parameterCount == 0) { - score = 1; - } + protected static Object execute( + V8Runtime v8Runtime, Object targetObject, List executables, JavetVirtualObject[] javetVirtualObjects) + throws Throwable { + List> scoredExecutables = new ArrayList<>(); + for (E executable : executables) { + ScoredExecutable scoredExecutable = new ScoredExecutable( + v8Runtime, targetObject, executable, javetVirtualObjects); + scoredExecutable.calculateScore(); + double score = scoredExecutable.getScore(); + if (score > 0) { + scoredExecutables.add(scoredExecutable); } - } else { - boolean isVarArgs = isMethodVarArgs && length >= parameterCount - 1; - boolean isFixedArgs = !isMethodVarArgs && length == parameterCount; - if (isVarArgs || isFixedArgs) { - double totalScore = 0; - final int fixedParameterCount = isMethodVarArgs ? parameterCount - 1 : parameterCount; - for (int i = 0; i < fixedParameterCount; i++) { - Class parameterType = parameterTypes[i]; - Object object = objects[i]; - if (object == null) { - if (parameterType.isPrimitive()) { - totalScore = 0; - break; - } else { - totalScore += 1; - } - } else if (parameterType.isAssignableFrom(object.getClass())) { - totalScore += 1; - } else if (parameterType.isPrimitive() - && JavetPrimitiveUtils.toExactPrimitive(parameterType, object) != null) { - totalScore += 0.9; - } else { - totalScore = 0; - break; - } - } - if ((fixedParameterCount == 0 || (fixedParameterCount > 0 && totalScore > 0)) - && isMethodVarArgs && length >= parameterCount) { - Class componentType = parameterTypes[fixedParameterCount].getComponentType(); - for (int i = fixedParameterCount; i < length; ++i) { - Object object = objects[i]; - if (object == null) { - if (componentType.isPrimitive()) { - totalScore = 0; - break; - } else { - totalScore += 1; - } - } else if (componentType.isAssignableFrom(object.getClass())) { - totalScore += 1; - } else if (componentType.isPrimitive() - && JavetPrimitiveUtils.toExactPrimitive(componentType, object) != null) { - totalScore += 0.8; - } else { - totalScore = 0; - break; - } - } - } - if (totalScore > 0) { - score = totalScore / length; + } + if (!scoredExecutables.isEmpty()) { + scoredExecutables.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore())); + Throwable lastException = null; + for (ScoredExecutable scoredExecutable : scoredExecutables) { + try { + return scoredExecutable.execute(); + } catch (Throwable t) { + lastException = t; } } + throw lastException; } - return score; + return null; } /** @@ -287,81 +270,21 @@ protected void addMethod(Method method, int startIndex, Map @V8Function @Override public V8Value construct(V8Value target, V8ValueArray arguments, V8Value newTarget) throws JavetException { - Object[] objects = ((ArrayList) v8Runtime.toObject(arguments)).toArray(); - final int length = objects.length; - List> sortedConstructors = new ArrayList<>(); - for (Constructor constructor : constructors) { - double score = calculateScore(constructor, objects); - if (score > 0) { - sortedConstructors.add(new ScoredExecutable(score, constructor)); - } - } - if (!sortedConstructors.isEmpty()) { - sortedConstructors.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore())); - Throwable lastException = null; - for (ScoredExecutable scoredConstructor : sortedConstructors) { - Constructor constructor = scoredConstructor.getExecutable(); - final int parameterCount = constructor.getParameterCount(); - Class[] parameterTypes = constructor.getParameterTypes(); - boolean isMethodVarArgs = constructor.isVarArgs(); - try { - if (length == 0) { - if (isMethodVarArgs) { - Class componentType = parameterTypes[parameterCount - 1]; - Object varObject = Array.newInstance(componentType, 0); - return v8Runtime.toV8Value(constructor.newInstance(varObject)); - } else { - return v8Runtime.toV8Value(constructor.newInstance()); - } - } else { - List parameters = new ArrayList<>(); - if (isMethodVarArgs) { - for (int i = 0; i < parameterCount; i++) { - Class parameterType = parameterTypes[i]; - if (parameterType.isArray() && i == parameterCount - 1) { - Class componentType = parameterType.getComponentType(); - Object varObject = Array.newInstance(componentType, length - i); - for (int j = i; j < length; ++j) { - Object parameter = objects[j]; - if (parameter != null && !componentType.isAssignableFrom(parameter.getClass()) - && componentType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(componentType, parameter); - } - Array.set(varObject, j - i, parameter); - } - parameters.add(varObject); - } else { - Object parameter = objects[i]; - if (parameter != null && !parameterType.isAssignableFrom(parameter.getClass()) - && parameterType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(parameterType, parameter); - } - parameters.add(parameter); - } - } - } else { - for (int i = 0; i < length; i++) { - Class parameterType = parameterTypes[i]; - Object parameter = objects[i]; - if (parameter != null && !parameterType.isAssignableFrom(parameter.getClass()) - && parameterType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(parameterType, parameter); - } - parameters.add(parameter); - } - } - return v8Runtime.toV8Value(constructor.newInstance(parameters.toArray())); - } - } catch (Throwable t) { - lastException = t; - } - } + V8Value[] v8Values = null; + try { + v8Values = arguments.toArray(); + return v8Runtime.toV8Value(execute( + v8Runtime, null, constructors, V8ValueUtils.convertToVirtualObjects(v8Values))); + } catch (JavetException e) { + throw e; + } catch (Throwable t) { throw new JavetException(JavetError.CallbackMethodFailure, SimpleMap.of( JavetError.PARAMETER_METHOD_NAME, METHOD_NAME_CONSTRUCTOR, - JavetError.PARAMETER_MESSAGE, lastException.getMessage()), lastException); + JavetError.PARAMETER_MESSAGE, t.getMessage()), t); + } finally { + JavetResourceUtils.safeClose((Object[]) v8Values); } - return v8Runtime.createV8ValueUndefined(); } @V8Function @@ -419,13 +342,13 @@ public V8Value get(V8Value target, V8Value property, V8Value receiver) throws Ja List methods = gettersMap.get(propertyName); if (methods != null) { JavetUniversalInterceptor javetUniversalInterceptor = - new JavetUniversalInterceptor(targetObject, propertyName, methods); + new JavetUniversalInterceptor(v8Runtime, targetObject, propertyName, methods); return v8Runtime.toV8Value(javetUniversalInterceptor.invoke()); } methods = methodsMap.get(propertyName); if (methods != null) { JavetUniversalInterceptor javetUniversalInterceptor = - new JavetUniversalInterceptor(targetObject, propertyName, methods); + new JavetUniversalInterceptor(v8Runtime, targetObject, propertyName, methods); return v8Runtime.createV8ValueFunction(javetUniversalInterceptor.getCallbackContext()); } } @@ -787,8 +710,8 @@ public V8ValueBoolean set(V8Value target, V8Value propertyKey, V8Value propertyV List methods = settersMap.get(propertyName); if (methods != null) { JavetUniversalInterceptor javetUniversalInterceptor = - new JavetUniversalInterceptor(targetObject, propertyName, methods); - javetUniversalInterceptor.invoke(valueObject); + new JavetUniversalInterceptor(v8Runtime, targetObject, propertyName, methods); + javetUniversalInterceptor.invoke(propertyValue); isSet = true; } } @@ -806,19 +729,23 @@ public static class JavetUniversalInterceptor { private String jsMethodName; private List methods; private Object targetObject; + private V8Runtime v8Runtime; /** * Instantiates a new Javet universal interceptor. * + * @param v8Runtime the V8 runtime * @param targetObject the target object * @param jsMethodName the JS method name * @param methods the methods * @since 0.9.6 */ - public JavetUniversalInterceptor(Object targetObject, String jsMethodName, List methods) { + public JavetUniversalInterceptor( + V8Runtime v8Runtime, Object targetObject, String jsMethodName, List methods) { this.jsMethodName = jsMethodName; this.methods = methods; this.targetObject = targetObject; + this.v8Runtime = v8Runtime; } @@ -832,7 +759,7 @@ public JavetCallbackContext getCallbackContext() { try { return new JavetCallbackContext( this, - getClass().getMethod(METHOD_NAME_INVOKE, Object[].class)); + getClass().getMethod(METHOD_NAME_INVOKE, V8Value[].class)); } catch (NoSuchMethodException e) { } return null; @@ -869,100 +796,24 @@ public Object getTargetObject() { } /** - * Invoke object. + * Invoke. * - * @param objects the objects + * @param v8Values the V8 values * @return the object * @throws JavetException the javet exception * @since 0.9.6 */ - public Object invoke(Object... objects) throws JavetException { - final int length = objects.length; - Object[] convertedObjects = new Object[length]; - for (int i = 0; i < length; i++) { - Object object = objects[i]; - if (object instanceof V8Value) { - V8Value v8Value = (V8Value) object; - convertedObjects[i] = v8Value.getV8Runtime().toObject(v8Value); - } else { - convertedObjects[i] = objects[i]; - } - } - objects = convertedObjects; - List> sortedMethods = new ArrayList<>(); - for (Method method : methods) { - double score = calculateScore(method, objects); - if (score > 0) { - sortedMethods.add(new ScoredExecutable(score, method)); - } - } - if (!sortedMethods.isEmpty()) { - sortedMethods.sort((o1, o2) -> Double.compare(o2.getScore(), o1.getScore())); - Throwable lastException = null; - for (ScoredExecutable scoredMethod : sortedMethods) { - Method method = scoredMethod.getExecutable(); - Object callee = Modifier.isStatic(method.getModifiers()) ? null : targetObject; - final int parameterCount = method.getParameterCount(); - Class[] parameterTypes = method.getParameterTypes(); - boolean isMethodVarArgs = method.isVarArgs(); - try { - if (length == 0) { - if (isMethodVarArgs) { - Class componentType = parameterTypes[parameterCount - 1]; - Object varObject = Array.newInstance(componentType, 0); - return method.invoke(callee, varObject); - } else { - return method.invoke(callee); - } - } else { - List parameters = new ArrayList<>(); - if (isMethodVarArgs) { - for (int i = 0; i < parameterCount; i++) { - Class parameterType = parameterTypes[i]; - if (parameterType.isArray() && i == parameterCount - 1) { - Class componentType = parameterType.getComponentType(); - Object varObject = Array.newInstance(componentType, length - i); - for (int j = i; j < length; ++j) { - Object parameter = objects[j]; - if (parameter != null && !componentType.isAssignableFrom(parameter.getClass()) - && componentType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(componentType, parameter); - } - Array.set(varObject, j - i, parameter); - } - parameters.add(varObject); - } else { - Object parameter = objects[i]; - if (parameter != null && !parameterType.isAssignableFrom(parameter.getClass()) - && parameterType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(parameterType, parameter); - } - parameters.add(parameter); - } - } - } else { - for (int i = 0; i < length; i++) { - Class parameterType = parameterTypes[i]; - Object parameter = objects[i]; - if (parameter != null && !parameterType.isAssignableFrom(parameter.getClass()) - && parameterType.isPrimitive()) { - parameter = JavetPrimitiveUtils.toExactPrimitive(parameterType, parameter); - } - parameters.add(parameter); - } - } - return method.invoke(callee, parameters.toArray()); - } - } catch (Throwable t) { - lastException = t; - } - } + public Object invoke(V8Value... v8Values) throws JavetException { + try { + return execute(v8Runtime, targetObject, methods, V8ValueUtils.convertToVirtualObjects(v8Values)); + } catch (JavetException e) { + throw e; + } catch (Throwable t) { throw new JavetException(JavetError.CallbackMethodFailure, SimpleMap.of( JavetError.PARAMETER_METHOD_NAME, jsMethodName, - JavetError.PARAMETER_MESSAGE, lastException.getMessage()), lastException); + JavetError.PARAMETER_MESSAGE, t.getMessage()), t); } - return null; } } @@ -974,28 +825,251 @@ public Object invoke(Object... objects) throws JavetException { */ public static class ScoredExecutable { private E executable; + private JavetVirtualObject[] javetVirtualObjects; private double score; + private Object targetObject; + private V8Runtime v8Runtime; /** * Instantiates a new Scored executable. * - * @param score the score - * @param executable the executable - * @since 0.9.6 + * @param v8Runtime the V8 runtime + * @param targetObject the target object + * @param executable the executable + * @param javetVirtualObjects the javet virtual objects + * @since 0.9.10 */ - public ScoredExecutable(double score, E executable) { - this.score = score; + public ScoredExecutable( + V8Runtime v8Runtime, Object targetObject, E executable, JavetVirtualObject[] javetVirtualObjects) { this.executable = executable; + this.javetVirtualObjects = javetVirtualObjects; + this.score = 0; + this.targetObject = targetObject; + this.v8Runtime = v8Runtime; } /** - * Gets method. + * Calculate score double. * - * @return the method - * @since 0.9.6 + * @throws JavetException the javet exception + * @since 0.9.10 */ - public E getExecutable() { - return executable; + public void calculateScore() throws JavetException { + // Max score is 1. Min score is 0. + final int parameterCount = executable.getParameterCount(); + Class[] parameterTypes = executable.getParameterTypes(); + boolean isExecutableVarArgs = executable.isVarArgs(); + score = 0; + final int length = javetVirtualObjects.length; + if (length == 0) { + if (isExecutableVarArgs) { + if (parameterCount == 1) { + score = 0.99; + } + } else { + if (parameterCount == 0) { + score = 1; + } + } + } else { + boolean isVarArgs = isExecutableVarArgs && length >= parameterCount - 1; + boolean isFixedArgs = !isExecutableVarArgs && length == parameterCount; + if (isVarArgs || isFixedArgs) { + double totalScore = 0; + final int fixedParameterCount = isExecutableVarArgs ? parameterCount - 1 : parameterCount; + for (int i = 0; i < fixedParameterCount; i++) { + Class parameterType = parameterTypes[i]; + final V8Value v8Value = javetVirtualObjects[i].getV8Value(); + if (v8Value != null) { + if (V8_VALUE_CLASS.isAssignableFrom(parameterType) + && parameterType.isAssignableFrom(v8Value.getClass())) { + totalScore += 1; + continue; + } else if (parameterType.isInterface()) { + if (V8_VALUE_FUNCTION_CLASS.isAssignableFrom(v8Value.getClass())) { + totalScore += 0.95; + continue; + } else if (!V8_VALUE_PROXY_CLASS.isAssignableFrom(v8Value.getClass()) + && V8_VALUE_OBJECT_CLASS.isAssignableFrom(v8Value.getClass())) { + totalScore += 0.85; + continue; + } + } + } + final Object object = javetVirtualObjects[i].getObject(); + if (object == null) { + if (parameterType.isPrimitive()) { + totalScore = 0; + break; + } + totalScore += 0.9; + } else if (parameterType.isAssignableFrom(object.getClass())) { + totalScore += 0.9; + } else if (parameterType.isPrimitive() + && JavetPrimitiveUtils.toExactPrimitive(parameterType, object) != null) { + totalScore += 0.8; + } else { + totalScore = 0; + break; + } + } + if ((fixedParameterCount == 0 || (fixedParameterCount > 0 && totalScore > 0)) && isVarArgs) { + Class componentType = parameterTypes[fixedParameterCount].getComponentType(); + for (int i = fixedParameterCount; i < length; ++i) { + final V8Value v8Value = javetVirtualObjects[i].getV8Value(); + if (v8Value != null) { + if (V8_VALUE_CLASS.isAssignableFrom(componentType) + && componentType.isAssignableFrom(v8Value.getClass())) { + totalScore += 0.95; + continue; + } else if (componentType.isInterface()) { + if (V8_VALUE_FUNCTION_CLASS.isAssignableFrom(v8Value.getClass())) { + totalScore += 0.95; + continue; + } else if (!V8_VALUE_PROXY_CLASS.isAssignableFrom(v8Value.getClass()) + && V8_VALUE_OBJECT_CLASS.isAssignableFrom(v8Value.getClass())) { + totalScore += 0.85; + continue; + } + } + } + final Object object = javetVirtualObjects[i].getObject(); + if (object == null) { + if (componentType.isPrimitive()) { + totalScore = 0; + break; + } else { + totalScore += 0.85; + } + } else if (componentType.isAssignableFrom(object.getClass())) { + totalScore += 0.85; + } else if (componentType.isPrimitive() + && JavetPrimitiveUtils.toExactPrimitive(componentType, object) != null) { + totalScore += 0.75; + } else { + totalScore = 0; + break; + } + } + } + if (totalScore > 0) { + score = totalScore / length; + if (targetObject != null && executable.getDeclaringClass() != targetObject.getClass()) { + score *= 0.9; + } + } + } + } + } + + /** + * Execute. + * + * @return the object + * @throws Throwable the throwable + * @since 0.9.10 + */ + public Object execute() throws Throwable { + final int length = javetVirtualObjects.length; + Object callee = Modifier.isStatic(executable.getModifiers()) ? null : targetObject; + final int parameterCount = executable.getParameterCount(); + Class[] parameterTypes = executable.getParameterTypes(); + boolean isExecutableVarArgs = executable.isVarArgs(); + if (length == 0) { + if (isExecutableVarArgs) { + Class componentType = parameterTypes[parameterCount - 1]; + Object varObject = Array.newInstance(componentType, 0); + if (executable instanceof Constructor) { + return ((Constructor) executable).newInstance(varObject); + } else { + return ((Method) executable).invoke(callee, varObject); + } + } else { + if (executable instanceof Constructor) { + return ((Constructor) executable).newInstance(); + } else { + return ((Method) executable).invoke(callee); + } + } + } else { + List parameters = new ArrayList<>(); + final int fixedParameterCount = isExecutableVarArgs ? parameterCount - 1 : parameterCount; + for (int i = 0; i < fixedParameterCount; i++) { + Class parameterType = parameterTypes[i]; + final V8Value v8Value = javetVirtualObjects[i].getV8Value(); + final Object object = javetVirtualObjects[i].getObject(); + Object parameter = object; + if (v8Value != null) { + if (V8_VALUE_CLASS.isAssignableFrom(parameterType) + && parameterType.isAssignableFrom(v8Value.getClass())) { + parameter = v8Value; + } else if (parameterType.isInterface()) { + if (V8_VALUE_FUNCTION_CLASS.isAssignableFrom(v8Value.getClass())) { + DynamicProxyV8ValueFunctionInvocationHandler invocationHandler = + new DynamicProxyV8ValueFunctionInvocationHandler(v8Value.toClone()); + parameter = Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{parameterType, AutoCloseable.class}, + invocationHandler); + } else if (!V8_VALUE_PROXY_CLASS.isAssignableFrom(v8Value.getClass()) + && V8_VALUE_OBJECT_CLASS.isAssignableFrom(v8Value.getClass())) { + DynamicProxyV8ValueObjectInvocationHandler invocationHandler = + new DynamicProxyV8ValueObjectInvocationHandler(v8Value.toClone()); + parameter = Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{parameterType, AutoCloseable.class}, + invocationHandler); + } + } + } else if (object != null && !parameterType.isAssignableFrom(object.getClass()) + && parameterType.isPrimitive()) { + parameter = JavetPrimitiveUtils.toExactPrimitive(parameterType, object); + } + parameters.add(parameter); + } + if (isExecutableVarArgs) { + Class componentType = parameterTypes[fixedParameterCount].getComponentType(); + Object varObject = Array.newInstance(componentType, length - fixedParameterCount); + for (int i = fixedParameterCount; i < length; ++i) { + final V8Value v8Value = javetVirtualObjects[i].getV8Value(); + final Object object = javetVirtualObjects[i].getObject(); + Object parameter = object; + if (v8Value != null) { + if (V8_VALUE_CLASS.isAssignableFrom(componentType) + && componentType.isAssignableFrom(v8Value.getClass())) { + parameter = v8Value; + } else if (componentType.isInterface()) { + if (V8_VALUE_FUNCTION_CLASS.isAssignableFrom(v8Value.getClass())) { + DynamicProxyV8ValueFunctionInvocationHandler invocationHandler = + new DynamicProxyV8ValueFunctionInvocationHandler(v8Value.toClone()); + parameter = Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{componentType, AutoCloseable.class}, + invocationHandler); + } else if (!V8_VALUE_PROXY_CLASS.isAssignableFrom(v8Value.getClass()) + && V8_VALUE_OBJECT_CLASS.isAssignableFrom(v8Value.getClass())) { + DynamicProxyV8ValueObjectInvocationHandler invocationHandler = + new DynamicProxyV8ValueObjectInvocationHandler(v8Value.toClone()); + parameter = Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[]{componentType, AutoCloseable.class}, + invocationHandler); + } + } + } else if (object != null && !componentType.isAssignableFrom(object.getClass()) + && componentType.isPrimitive()) { + parameter = JavetPrimitiveUtils.toExactPrimitive(componentType, object); + } + Array.set(varObject, i - fixedParameterCount, parameter); + } + parameters.add(varObject); + } + if (executable instanceof Constructor) { + return ((Constructor) executable).newInstance(parameters.toArray()); + } else { + return ((Method) executable).invoke(callee, parameters.toArray()); + } + } } /** diff --git a/src/main/java/com/caoccao/javet/node/modules/BaseNodeModule.java b/src/main/java/com/caoccao/javet/node/modules/BaseNodeModule.java index cf7edbd30..7db15579d 100644 --- a/src/main/java/com/caoccao/javet/node/modules/BaseNodeModule.java +++ b/src/main/java/com/caoccao/javet/node/modules/BaseNodeModule.java @@ -21,18 +21,21 @@ import com.caoccao.javet.utils.JavetResourceUtils; import com.caoccao.javet.values.reference.V8ValueObject; +import java.util.Objects; + public abstract class BaseNodeModule implements INodeModule { protected V8ValueObject moduleObject; protected String name; public BaseNodeModule(V8ValueObject moduleObject, String name) { - this.moduleObject = moduleObject; + this.moduleObject = Objects.requireNonNull(moduleObject); this.name = name; } @Override public void close() throws JavetException { JavetResourceUtils.safeClose(moduleObject); + moduleObject = null; } @Override @@ -44,4 +47,9 @@ public String getName() { return name; } + @Override + public boolean isClosed() { + return moduleObject == null || moduleObject.isClosed(); + } + } diff --git a/src/main/java/com/caoccao/javet/utils/JavetResourceUtils.java b/src/main/java/com/caoccao/javet/utils/JavetResourceUtils.java index 492abce60..b9e4f878c 100644 --- a/src/main/java/com/caoccao/javet/utils/JavetResourceUtils.java +++ b/src/main/java/com/caoccao/javet/utils/JavetResourceUtils.java @@ -21,7 +21,6 @@ import com.caoccao.javet.interfaces.IJavetClosable; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.reference.IV8ValueReference; -import com.caoccao.javet.values.reference.V8ValueReference; import java.util.Collection; @@ -29,6 +28,13 @@ public final class JavetResourceUtils { private JavetResourceUtils() { } + public static boolean isClosed(Object object) { + if (object instanceof IJavetClosable) { + return ((IJavetClosable) object).isClosed(); + } + return true; + } + public static void safeClose(Object... objects) { for (Object object : objects) { safeClose(object); @@ -42,7 +48,7 @@ public static void safeClose(Object object) { if (object instanceof IV8ValueReference) { try { IV8ValueReference iV8ValueReference = (IV8ValueReference) object; - if (iV8ValueReference.getHandle() != V8ValueReference.INVALID_HANDLE) { + if (!iV8ValueReference.isClosed()) { iV8ValueReference.close(); } } catch (JavetException ignored) { diff --git a/src/main/java/com/caoccao/javet/utils/JavetVirtualObject.java b/src/main/java/com/caoccao/javet/utils/JavetVirtualObject.java new file mode 100644 index 000000000..3693fa2d5 --- /dev/null +++ b/src/main/java/com/caoccao/javet/utils/JavetVirtualObject.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021. caoccao.com Sam Cao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.caoccao.javet.utils; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8Value; + +/** + * The type Javet virtual object. + * + * @since 0.9.10 + */ +public class JavetVirtualObject { + /** + * The Converted. + * + * @since 0.9.10 + */ + protected boolean converted; + /** + * The Object. + * + * @since 0.9.10 + */ + protected Object object; + /** + * The V8 value. + * + * @since 0.9.10 + */ + protected V8Value v8Value; + + /** + * Instantiates a new Javet virtual object. + * + * @param v8Value the V8 value + * @since 0.9.10 + */ + public JavetVirtualObject(V8Value v8Value) { + converted = false; + object = null; + this.v8Value = v8Value; + } + + /** + * Gets object. + * + * @return the object + * @throws JavetException the javet exception + * @since 0.9.10 + */ + public Object getObject() throws JavetException { + if (!converted) { + object = v8Value.getV8Runtime().toObject(v8Value); + converted = true; + } + return object; + } + + /** + * Gets V8 value. + * + * @return the V8 value + * @since 0.9.10 + */ + public V8Value getV8Value() { + return v8Value; + } + + /** + * Is converted. + * + * @return the boolean + * @since 0.9.10 + */ + public boolean isConverted() { + return converted; + } +} diff --git a/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java b/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java index 40416f0eb..a032ab82f 100644 --- a/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java +++ b/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java @@ -22,12 +22,25 @@ import java.util.Arrays; import java.util.stream.Collectors; +/** + * The type V8 value utils. + */ public final class V8ValueUtils { + /** + * The constant EMPTY. + */ public static final String EMPTY = ""; private V8ValueUtils() { } + /** + * Concat string. + * + * @param delimiter the delimiter + * @param v8Values the V8 values + * @return the string + */ public static String concat(String delimiter, V8Value... v8Values) { if (v8Values == null || v8Values.length == 0) { return EMPTY; @@ -37,4 +50,19 @@ public static String concat(String delimiter, V8Value... v8Values) { } return Arrays.stream(v8Values).map(V8Value::toString).collect(Collectors.joining(delimiter)); } + + /** + * Convert to virtual objects. + * + * @param v8Values the V8 values + * @return the javet virtual objects + */ + public static JavetVirtualObject[] convertToVirtualObjects(V8Value... v8Values) { + final int length = v8Values.length; + JavetVirtualObject[] javetVirtualObjects = new JavetVirtualObject[length]; + for (int i = 0; i < length; ++i) { + javetVirtualObjects[i] = new JavetVirtualObject(v8Values[i]); + } + return javetVirtualObjects; + } } diff --git a/src/main/java/com/caoccao/javet/values/V8Value.java b/src/main/java/com/caoccao/javet/values/V8Value.java index c31583f1c..a316da64e 100644 --- a/src/main/java/com/caoccao/javet/values/V8Value.java +++ b/src/main/java/com/caoccao/javet/values/V8Value.java @@ -47,6 +47,11 @@ public V8Runtime getV8Runtime() { return v8Runtime; } + @Override + public boolean isClosed() { + return v8Runtime == null || v8Runtime.isClosed(); + } + @Override public abstract boolean sameValue(V8Value v8Value) throws JavetException; diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java index e30e3f55a..da8ef6cc5 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java @@ -95,4 +95,14 @@ default int pushNull() throws JavetException { default int pushUndefined() throws JavetException { return push(getV8Runtime().createV8ValueUndefined()); } + + @CheckReturnValue + default V8Value[] toArray() throws JavetException { + final int length = getLength(); + V8Value[] v8Values = new V8Value[length]; + for (int i = 0; i < length; ++i) { + v8Values[i] = get(i); + } + return v8Values; + } } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java index 5dde93dcd..979dbfd90 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java @@ -30,6 +30,8 @@ public interface IV8ValueReference extends IV8Value { V8ValueReferenceType getType(); + boolean isClosed(); + boolean isWeak() throws JavetException; boolean isWeak(boolean forceSync) throws JavetException; diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java index d90ee6635..b34a27e5f 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java @@ -42,7 +42,7 @@ protected void addReference() throws JavetException { @Override public void checkV8Runtime() throws JavetException { - if (handle == INVALID_HANDLE) { + if (isClosed()) { throw new JavetException(JavetError.RuntimeAlreadyRegistered); } super.checkV8Runtime(); @@ -62,7 +62,7 @@ public void close() throws JavetException { @Override public void close(boolean forceClose) throws JavetException { - if (handle == INVALID_HANDLE) { + if (isClosed()) { throw new JavetException(JavetError.RuntimeAlreadyClosed); } if (forceClose || !isWeak()) { @@ -96,6 +96,11 @@ public long getHandle() { @Override public abstract V8ValueReferenceType getType(); + @Override + public boolean isClosed() { + return handle == INVALID_HANDLE || super.isClosed(); + } + @Override public boolean isWeak() { return weak; diff --git a/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValue.java b/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValue.java index 31c407b83..882923c31 100644 --- a/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValue.java +++ b/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValue.java @@ -68,6 +68,7 @@ public void close() throws JavetException { if (converted) { JavetResourceUtils.safeClose(value); } + value = null; } /** @@ -80,4 +81,9 @@ public void close() throws JavetException { public T get() { return (T) value; } + + @Override + public boolean isClosed() { + return value == null || value.isClosed(); + } } diff --git a/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValueList.java b/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValueList.java index bc4408fce..48f0b8ca9 100644 --- a/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValueList.java +++ b/src/main/java/com/caoccao/javet/values/virtual/V8VirtualValueList.java @@ -76,6 +76,8 @@ public V8VirtualValueList(V8Runtime v8Runtime, Object... objects) throws JavetEx @Override public void close() throws JavetException { JavetResourceUtils.safeClose(toBeClosedValues); + toBeClosedValues = null; + values = null; } /** @@ -87,4 +89,9 @@ public void close() throws JavetException { public V8Value[] get() { return values == null ? new V8Value[0] : values.toArray(new V8Value[0]); } + + @Override + public boolean isClosed() { + return values == null; + } } diff --git a/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java b/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java index bd69567d9..376b7e3d6 100644 --- a/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java +++ b/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java @@ -52,6 +52,7 @@ public void afterEach() throws JavetException { assertEquals(nodeRuntime.getNodeModuleCount(), nodeRuntime.getReferenceCount(), "Reference count should be equal to node module count after test case is ended."); nodeRuntime.close(); + assertTrue(nodeRuntime.isClosed()); assertEquals(0, v8Host.getV8RuntimeCount()); } diff --git a/src/test/java/com/caoccao/javet/interop/TestV8Locker.java b/src/test/java/com/caoccao/javet/interop/TestV8Locker.java index d0cbf1ce7..78c0b2411 100644 --- a/src/test/java/com/caoccao/javet/interop/TestV8Locker.java +++ b/src/test/java/com/caoccao/javet/interop/TestV8Locker.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TestV8Locker extends BaseTestJavetRuntime { @Test @@ -43,6 +44,7 @@ public void testExceptionInClose() throws JavetException { V8Locker v8Locker = v8Runtime.getV8Locker(); assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger()); v8Locker.close(); + assertTrue(v8Locker.isClosed()); try { v8Locker.close(); } catch (JavetException e) { diff --git a/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java b/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java index 322ab1614..23bdcb6ac 100644 --- a/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java +++ b/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java @@ -50,8 +50,12 @@ public void testAllowEval() throws JavetException { @Test public void testClose() throws JavetException { + V8Runtime danglingV8Runtime; try (V8Runtime v8Runtime = v8Host.createV8Runtime("window")) { + assertFalse(v8Runtime.isClosed()); + danglingV8Runtime = v8Runtime; } + assertTrue(danglingV8Runtime.isClosed()); } @Test diff --git a/src/test/java/com/caoccao/javet/interop/converters/TestJavetProxyConverter.java b/src/test/java/com/caoccao/javet/interop/converters/TestJavetProxyConverter.java index efeb51231..f4bcb3b93 100644 --- a/src/test/java/com/caoccao/javet/interop/converters/TestJavetProxyConverter.java +++ b/src/test/java/com/caoccao/javet/interop/converters/TestJavetProxyConverter.java @@ -22,15 +22,13 @@ import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.exceptions.JavetExecutionException; import com.caoccao.javet.interfaces.IJavetClosable; +import com.caoccao.javet.mock.MockCallbackReceiver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Path; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.*; @@ -46,6 +44,41 @@ public void beforeEach() throws JavetException { v8Runtime.setConverter(javetProxyConverter); } + @Test + public void testAnonymousFunction() throws Exception { + try (StringJoiner stringJoiner = new StringJoiner()) { + v8Runtime.getGlobalObject().set("stringJoiner", stringJoiner); + v8Runtime.getExecutor("stringJoiner.setJoiner((a, b) => a + ',' + b);").executeVoid(); + IStringJoiner joiner = stringJoiner.getJoiner(); + assertEquals("a,b", joiner.join("a", "b")); + assertEquals("a,b,c", joiner.join(joiner.join("a", "b"), "c")); + v8Runtime.getGlobalObject().delete("stringJoiner"); + } + v8Runtime.lowMemoryNotification(); + } + + @Test + public void testAnonymousObject() throws Exception { + try (StringUtils stringUtils = new StringUtils()) { + v8Runtime.getGlobalObject().set("stringUtils", stringUtils); + v8Runtime.getExecutor( + "stringUtils.setUtils({\n" + + " hello: () => 'hello',\n" + + " join: (separator, ...strings) => [...strings].join(separator),\n" + + " split: (separator, str) => str.split(separator),\n" + + "});" + ).executeVoid(); + IStringUtils utils = stringUtils.getUtils(); + assertEquals("hello", utils.hello()); + assertEquals("a,b,c", utils.join(",", "a", "b", "c")); + assertArrayEquals( + new String[]{"a", "b", "c"}, + utils.split(",", "a,b,c").toArray(new String[0])); + v8Runtime.getGlobalObject().delete("stringUtils"); + } + v8Runtime.lowMemoryNotification(); + } + @Test public void testConstructor() throws JavetException { v8Runtime.getGlobalObject().set("StringBuilder", StringBuilder.class); @@ -127,6 +160,18 @@ public void testMap() throws JavetException { javetProxyConverter.getConfig().setProxyMapEnabled(false); } + @Test + public void testMockCallbackReceiver() throws JavetException { + MockCallbackReceiver mockCallbackReceiver = new MockCallbackReceiver(v8Runtime); + v8Runtime.getGlobalObject().set("m", mockCallbackReceiver); + assertEquals("abc", v8Runtime.getExecutor("m.echo('abc')").executeString()); + assertEquals( + "[\"abc\",\"def\"]", + v8Runtime.getExecutor("JSON.stringify(m.echo('abc', 'def'))").executeString()); + v8Runtime.getGlobalObject().delete("m"); + v8Runtime.lowMemoryNotification(); + } + @Test public void testPath() throws JavetException { Path path = new File("/tmp/i-am-not-accessible").toPath(); @@ -175,4 +220,64 @@ public void testSet() throws JavetException { v8Runtime.lowMemoryNotification(); javetProxyConverter.getConfig().setProxySetEnabled(false); } + + interface IStringJoiner extends AutoCloseable { + String join(String a, String b); + } + + interface IStringUtils extends AutoCloseable { + String hello(); + + String join(String separator, String... strings); + + List split(String separator, String string); + } + + static class StringJoiner implements AutoCloseable { + private IStringJoiner joiner; + + public StringJoiner() { + joiner = null; + } + + @Override + public void close() throws Exception { + if (joiner != null) { + joiner.close(); + joiner = null; + } + } + + public IStringJoiner getJoiner() { + return joiner; + } + + public void setJoiner(IStringJoiner joiner) { + this.joiner = joiner; + } + } + + static class StringUtils implements AutoCloseable { + private IStringUtils utils; + + public StringUtils() { + utils = null; + } + + @Override + public void close() throws Exception { + if (utils != null) { + utils.close(); + utils = null; + } + } + + public IStringUtils getUtils() { + return utils; + } + + public void setUtils(IStringUtils utils) { + this.utils = utils; + } + } } diff --git a/src/test/java/com/caoccao/javet/interop/engine/TestJavetEnginePool.java b/src/test/java/com/caoccao/javet/interop/engine/TestJavetEnginePool.java index d2d65bc36..2785278fc 100644 --- a/src/test/java/com/caoccao/javet/interop/engine/TestJavetEnginePool.java +++ b/src/test/java/com/caoccao/javet/interop/engine/TestJavetEnginePool.java @@ -44,6 +44,7 @@ private void afterEach() throws JavetException { assertEquals(0, javetEnginePool.getActiveEngineCount()); assertEquals(0, javetEnginePool.getIdleEngineCount()); assertFalse(javetEnginePool.isActive()); + assertTrue(javetEnginePool.isClosed()); assertEquals(0, v8Host.getV8RuntimeCount()); } @@ -51,6 +52,7 @@ private void afterEach() throws JavetException { private void beforeEach() { javetEnginePool = new JavetEnginePool(); assertTrue(javetEnginePool.isActive()); + assertFalse(javetEnginePool.isClosed()); javetEngineConfig = javetEnginePool.getConfig(); javetEngineConfig.setPoolDaemonCheckIntervalMillis(TEST_POOL_DAEMON_CHECK_INTERVAL_MILLIS); javetEngineConfig.setJSRuntimeType(v8Host.getJSRuntimeType()); diff --git a/src/test/java/com/caoccao/javet/mock/MockCallbackReceiver.java b/src/test/java/com/caoccao/javet/mock/MockCallbackReceiver.java index 1b94269b0..98b8bacf0 100644 --- a/src/test/java/com/caoccao/javet/mock/MockCallbackReceiver.java +++ b/src/test/java/com/caoccao/javet/mock/MockCallbackReceiver.java @@ -50,6 +50,7 @@ public V8Value echo(V8Value arg) throws JavetException { return super.echo(arg); } + @Override public V8ValueArray echo(V8Value... args) throws JavetException { called = true; return super.echo(args); diff --git a/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInNodeJSMode.java b/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInNodeJSMode.java index e5f2eaf07..1e25a31eb 100644 --- a/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInNodeJSMode.java +++ b/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInNodeJSMode.java @@ -66,6 +66,11 @@ public IJavetLogger getLogger() { return iJavetEnginePool.getConfig().getJavetLogger(); } + @Override + public boolean isClosed() { + return iJavetEngine.isClosed(); + } + public void test() throws JavetException { NodeRuntime nodeRuntime = iJavetEngine.getV8Runtime(); Path workingDirectory = new File(JavetOSUtils.WORKING_DIRECTORY, "scripts/node/test-node").toPath(); diff --git a/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInV8Mode.java b/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInV8Mode.java index e0c1f22d2..60058bee2 100644 --- a/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInV8Mode.java +++ b/src/test/java/com/caoccao/javet/tutorial/DecimalJavetInV8Mode.java @@ -68,6 +68,11 @@ public IJavetLogger getLogger() { return iJavetEnginePool.getConfig().getJavetLogger(); } + @Override + public boolean isClosed() { + return iJavetEngine.isClosed(); + } + public void loadJS() throws JavetException { File decimalJSFile = new File( JavetOSUtils.WORKING_DIRECTORY,