From 932ee83d7a9606a831e087e5750a8b1b3ff6d1b7 Mon Sep 17 00:00:00 2001 From: Sam Cao Date: Tue, 2 Mar 2021 10:14:32 +0800 Subject: [PATCH] Javet v0.7.2 (#5) * Added ``setFunction(String functionName, String codeString)`` to ``IV8ValueObject`` * Added ``equals()`` and ``strictEquals()`` and ``sameValue()`` to ``IV8Value`` * Added ``getIdentityHash()`` to ``IV8ValueReference`` * Added ``isDead()``, ``isInUse()``, ``callAsConstructor()`` and ``terminateExecution()`` to ``V8Runtime`` * Added V8 typed array and data view * Added ``IJavetEngineGuard`` --- README.rst | 24 +- build.gradle.kts | 2 +- cpp/build.cmd | 2 +- cpp/build.sh | 2 +- .../com_caoccao_javet_interop_V8Native.cpp | 92 +++- cpp/jni/com_caoccao_javet_interop_V8Native.h | 64 +++ cpp/jni/javet.rc | 12 +- cpp/jni/javet_converter.cpp | 93 ++-- cpp/jni/javet_converter.h | 18 +- cpp/jni/javet_enums.h | 47 +- cpp/jni/javet_exceptions.cpp | 85 ++-- cpp/jni/javet_exceptions.h | 2 + docs/development/best_practices.rst | 28 ++ docs/{ => development}/build.rst | 2 +- docs/development/design.rst | 58 +++ docs/development/index.rst | 11 + docs/{ => development}/performance.rst | 10 +- .../tools.rst} | 28 +- docs/faq/history_with_j2v8.rst | 1 + docs/faq/index.rst | 1 + docs/faq/what_is_the_motivation.rst | 13 + docs/release_notes.rst | 10 + .../images/javet_architecture.drawio | 1 + docs/resources/images/javet_architecture.png | Bin 0 -> 156650 bytes .../resources/images/javet_engine_pool.drawio | 1 + docs/resources/images/javet_engine_pool.png | Bin 0 -> 72147 bytes docs/todo_list.rst | 4 +- docs/tutorial/hello_javet.rst | 21 +- docs/tutorial/index.rst | 3 + docs/tutorial/logging.rst | 79 +++ docs/tutorial/manipulate_v8_function.rst | 8 +- docs/tutorial/memory_management.rst | 41 +- docs/tutorial/polyfill.rst | 27 +- docs/tutorial/spring_integration.rst | 70 +++ docs/tutorial/termination.rst | 71 +++ pom.xml | 4 +- scripts/python/change_javet_version.py | 19 +- .../javet/entities/JavetEntityMap.java | 43 ++ .../exceptions/JavetTerminatedException.java | 33 ++ .../JavetV8RuntimeLockConflictException.java | 10 +- .../caoccao/javet/interop/IV8Creatable.java | 12 + .../caoccao/javet/interop/JavetLibLoader.java | 245 +++++----- .../com/caoccao/javet/interop/V8Host.java | 9 +- .../com/caoccao/javet/interop/V8Native.java | 17 + .../com/caoccao/javet/interop/V8Runtime.java | 155 +++++- .../javet/interop/engine/IJavetEngine.java | 8 + .../interop/engine/IJavetEngineGuard.java | 53 ++ .../interop/engine/IJavetEnginePool.java | 4 + .../javet/interop/engine/JavetEngine.java | 36 ++ .../interop/engine/JavetEngineConfig.java | 45 +- .../interop/engine/JavetEngineGuard.java | 109 +++++ .../javet/interop/engine/JavetEnginePool.java | 80 +-- .../javet/utils/JavetDefaultLogger.java | 12 +- .../com/caoccao/javet/utils/V8ValueUtils.java | 35 +- .../utils/converters/IJavetConverter.java | 3 +- .../converters/JavetObjectConverter.java | 154 ++++-- .../converters/JavetPrimitiveConverter.java | 13 +- .../com/caoccao/javet/values/IV8Value.java | 62 ++- .../com/caoccao/javet/values/V8Value.java | 11 +- .../javet/values/V8ValueReferenceType.java | 15 + .../javet/values/primitive/V8ValueDouble.java | 12 + .../javet/values/primitive/V8ValueNull.java | 19 +- .../values/primitive/V8ValuePrimitive.java | 21 + .../values/primitive/V8ValueUndefined.java | 17 +- .../values/primitive/V8ValueUnknown.java | 1 + .../javet/values/reference/IV8ValueArray.java | 8 +- .../values/reference/IV8ValueFunction.java | 7 + .../values/reference/IV8ValueIterator.java | 36 ++ .../reference/IV8ValueKeyContainer.java | 24 +- .../javet/values/reference/IV8ValueMap.java | 4 +- .../values/reference/IV8ValueObject.java | 107 +++- .../values/reference/IV8ValueReference.java | 12 + .../javet/values/reference/IV8ValueSet.java | 8 +- .../values/reference/IV8ValueTypedArray.java | 34 ++ .../values/reference/V8ValueArguments.java | 2 +- .../javet/values/reference/V8ValueArray.java | 28 +- .../values/reference/V8ValueArrayBuffer.java | 149 ++++++ .../values/reference/V8ValueDataView.java | 172 +++++++ .../javet/values/reference/V8ValueError.java | 2 +- .../values/reference/V8ValueFunction.java | 10 +- .../values/reference/V8ValueGlobalObject.java | 14 +- .../values/reference/V8ValueIterator.java | 49 ++ .../javet/values/reference/V8ValueMap.java | 30 +- .../javet/values/reference/V8ValueObject.java | 24 +- .../values/reference/V8ValuePromise.java | 2 +- .../javet/values/reference/V8ValueProxy.java | 2 +- .../values/reference/V8ValueReference.java | 51 +- .../javet/values/reference/V8ValueRegExp.java | 2 +- .../javet/values/reference/V8ValueSet.java | 23 +- .../javet/values/reference/V8ValueSymbol.java | 2 +- .../values/reference/V8ValueTypedArray.java | 455 ++++++++++++++++++ .../com/caoccao/javet/BaseTestJavetPool.java | 40 ++ .../caoccao/javet/interop/TestV8Native.java | 3 +- .../caoccao/javet/interop/TestV8Runtime.java | 44 +- .../interop/engine/TestJavetEngineGuard.java | 65 +++ .../interop/{ => engine}/TestPerformance.java | 31 +- .../caoccao/javet/tutorial/DecimalJavet.java | 53 +- .../converters/TestJavetObjectConverter.java | 247 ++++++++++ .../TestJavetPrimitiveConverter.java | 86 ++++ .../values/primitive/TestV8ValueBoolean.java | 9 + .../values/primitive/TestV8ValueDouble.java | 26 +- .../values/primitive/TestV8ValueInteger.java | 13 +- .../values/primitive/TestV8ValueLong.java | 23 +- .../values/primitive/TestV8ValueNull.java | 12 +- .../values/primitive/TestV8ValueString.java | 13 +- .../primitive/TestV8ValueUndefined.java | 12 +- .../primitive/TestV8ValueZonedDateTime.java | 12 + .../values/reference/TestV8ValueArray.java | 19 +- .../reference/TestV8ValueArrayBuffer.java | 63 +++ .../values/reference/TestV8ValueDataView.java | 70 +++ .../values/reference/TestV8ValueFunction.java | 22 +- .../values/reference/TestV8ValueMap.java | 50 +- .../values/reference/TestV8ValueObject.java | 35 +- .../values/reference/TestV8ValueSet.java | 10 +- .../reference/TestV8ValueTypedArray.java | 272 +++++++++++ 115 files changed, 4037 insertions(+), 638 deletions(-) create mode 100644 docs/development/best_practices.rst rename docs/{ => development}/build.rst (97%) create mode 100644 docs/development/design.rst create mode 100644 docs/development/index.rst rename docs/{ => development}/performance.rst (73%) rename docs/{development.rst => development/tools.rst} (65%) create mode 100644 docs/faq/what_is_the_motivation.rst create mode 100644 docs/resources/images/javet_architecture.drawio create mode 100644 docs/resources/images/javet_architecture.png create mode 100644 docs/resources/images/javet_engine_pool.drawio create mode 100644 docs/resources/images/javet_engine_pool.png create mode 100644 docs/tutorial/logging.rst create mode 100644 docs/tutorial/spring_integration.rst create mode 100644 docs/tutorial/termination.rst create mode 100644 src/main/java/com/caoccao/javet/entities/JavetEntityMap.java create mode 100644 src/main/java/com/caoccao/javet/exceptions/JavetTerminatedException.java create mode 100644 src/main/java/com/caoccao/javet/interop/engine/IJavetEngineGuard.java create mode 100644 src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/IV8ValueIterator.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/IV8ValueTypedArray.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/V8ValueArrayBuffer.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/V8ValueDataView.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/V8ValueIterator.java create mode 100644 src/main/java/com/caoccao/javet/values/reference/V8ValueTypedArray.java create mode 100644 src/test/java/com/caoccao/javet/BaseTestJavetPool.java create mode 100644 src/test/java/com/caoccao/javet/interop/engine/TestJavetEngineGuard.java rename src/test/java/com/caoccao/javet/interop/{ => engine}/TestPerformance.java (86%) create mode 100644 src/test/java/com/caoccao/javet/utils/converters/TestJavetObjectConverter.java create mode 100644 src/test/java/com/caoccao/javet/utils/converters/TestJavetPrimitiveConverter.java create mode 100644 src/test/java/com/caoccao/javet/values/reference/TestV8ValueArrayBuffer.java create mode 100644 src/test/java/com/caoccao/javet/values/reference/TestV8ValueDataView.java create mode 100644 src/test/java/com/caoccao/javet/values/reference/TestV8ValueTypedArray.java diff --git a/README.rst b/README.rst index e0829a5df..4ff53a24b 100644 --- a/README.rst +++ b/README.rst @@ -33,7 +33,7 @@ Maven com.caoccao.javet javet - 0.7.1 + 0.7.2 Gradle Kotlin DSL @@ -41,14 +41,14 @@ Gradle Kotlin DSL .. code-block:: kotlin - implementation("com.caoccao.javet:javet:0.7.1") + implementation("com.caoccao.javet:javet:0.7.2") Gradle Groovy DSL ^^^^^^^^^^^^^^^^^ .. code-block:: groovy - implementation 'com.caoccao.javet:javet:0.7.1' + implementation 'com.caoccao.javet:javet:0.7.2' Hello Javet ----------- @@ -62,25 +62,15 @@ Hello Javet Documents ========= -* `Build `_ -* `Development `_ +* `Development `_ + * `Build `_ + * `Design `_ + * `Performance `_ * `Tutorial `_ -* `Performance `_ * `Release Notes `_ * `FAQ `_ * `TODO List `_ -Motivation -========== - -I used to take a try of J2V8 and find it's quite compelling. However, J2V8 is slowly dying, with serious memory leak issues, V8 version issue, etc. - -Sometimes starting from scratch implies lower cost than upgrading an existing solution. I think it might be true here in this project. I've learned quite a lot by manually fixing the Windows and Linux build system of J2V8. - -Also, I had got many ideas on how the API will look like. At the end of 2020, I thought I would be able to write a new one from scratch and leave J2V8 behind. Indeed, I made it few months later. - -Please refer to `History with J2V8 `_ for detail. - License ======= diff --git a/build.gradle.kts b/build.gradle.kts index a3cd87abb..502f0d83e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ repositories { } group = "com.caoccao.javet" -version = "0.7.1" +version = "0.7.2" repositories { mavenCentral() diff --git a/cpp/build.cmd b/cpp/build.cmd index 57eb833bb..cb16ea9bc 100644 --- a/cpp/build.cmd +++ b/cpp/build.cmd @@ -1,6 +1,6 @@ @echo off REM Usage sample: build -DV8_DIR=C:\v8 -SET JAVET_VERSION=0.7.1 +SET JAVET_VERSION=0.7.2 rd /s/q build mkdir build cd build diff --git a/cpp/build.sh b/cpp/build.sh index a5a38dbac..827e3913e 100755 --- a/cpp/build.sh +++ b/cpp/build.sh @@ -1,7 +1,7 @@ #!/bin/sh # Usage sample: build -DV8_DIR=~/v8 -JAVET_VERSION=0.7.1 +JAVET_VERSION=0.7.2 rm -rf build mkdir build cd build diff --git a/cpp/jni/com_caoccao_javet_interop_V8Native.cpp b/cpp/jni/com_caoccao_javet_interop_V8Native.cpp index f7d22f1f6..21e8652ca 100644 --- a/cpp/jni/com_caoccao_javet_interop_V8Native.cpp +++ b/cpp/jni/com_caoccao_javet_interop_V8Native.cpp @@ -44,6 +44,7 @@ #define TO_JAVA_INTEGER(jniEnv, obj) jniEnv->CallIntMethod(obj, Javet::Main::jmethodIDV8ValueIntegerToPrimitive) #define TO_JAVA_STRING(jniEnv, obj) (jstring)jniEnv->CallObjectMethod(obj, Javet::Main::jmethodIDV8ValueStringToPrimitive) #define IS_V8_ARRAY(type) (type == Javet::Enums::V8ValueReferenceType::Array) +#define IS_V8_ARRAY_BUFFER(type) (type == Javet::Enums::V8ValueReferenceType::ArrayBuffer) #define IS_V8_ARGUMENTS(type) (type == Javet::Enums::V8ValueReferenceType::Arguments) #define IS_V8_FUNCTION(type) (type == Javet::Enums::V8ValueReferenceType::Function) #define IS_V8_MAP(type) (type == Javet::Enums::V8ValueReferenceType::Map) @@ -69,7 +70,18 @@ v8::HandleScope v8HandleScope(v8Runtime->v8Isolate); \ auto v8Context = v8::Local::New(v8Runtime->v8Isolate, v8Runtime->v8Context); \ v8::Context::Scope v8ContextScope(v8Context); \ - auto v8LocalObject = v8PersistentObjectPointer->Get(v8Runtime->v8Isolate); + auto v8LocalObject = v8PersistentObjectPointer->Get(v8Context->GetIsolate()); + +#define RUNTIME_AND_2_VALUES_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle1, v8ValueHandle2) \ + auto v8Runtime = reinterpret_cast(v8RuntimeHandle); \ + auto v8PersistentObjectPointer1 = reinterpret_cast*>(v8ValueHandle1); \ + auto v8PersistentObjectPointer2 = reinterpret_cast*>(v8ValueHandle2); \ + v8::Isolate::Scope v8IsolateScope(v8Runtime->v8Isolate); \ + v8::HandleScope v8HandleScope(v8Runtime->v8Isolate); \ + auto v8Context = v8::Local::New(v8Runtime->v8Isolate, v8Runtime->v8Context); \ + v8::Context::Scope v8ContextScope(v8Context); \ + auto v8LocalObject1 = v8PersistentObjectPointer1->Get(v8Context->GetIsolate()); \ + auto v8LocalObject2 = v8PersistentObjectPointer2->Get(v8Context->GetIsolate()); #define SAFE_CONVERT_AND_RETURN_JAVE_V8_VALUE(jniEnv, v8Context, v8Value) \ try { \ @@ -182,6 +194,30 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_call return nullptr; } +JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_callAsConstructor +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType, jobjectArray mValues) { + RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); + if (v8LocalObject->IsFunction()) { + v8::TryCatch v8TryCatch(v8Runtime->v8Isolate); + v8::MaybeLocal maybeLocalValueResult; + uint32_t valueCount = mValues == nullptr ? 0 : jniEnv->GetArrayLength(mValues); + if (valueCount > 0) { + auto umValuesPointer = Javet::Converter::ToV8Values(jniEnv, v8Context, mValues); + maybeLocalValueResult = v8LocalObject.As()->CallAsConstructor(v8Context, valueCount, umValuesPointer.get()); + } + else { + maybeLocalValueResult = v8LocalObject.As()->CallAsConstructor(v8Context, 0, nullptr); + } + if (v8TryCatch.HasCaught()) { + Javet::Exceptions::ThrowJavetExecutionException(jniEnv, v8Context, v8TryCatch); + } + else { + SAFE_CONVERT_AND_RETURN_JAVE_V8_VALUE(jniEnv, v8Context, maybeLocalValueResult.ToLocalChecked()); + } + } + return nullptr; +} + JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_clearWeak (JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType) { RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); @@ -259,6 +295,11 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_createV8Value else if (IS_V8_ARRAY(v8ValueType)) { v8ValueValue = v8::Array::New(v8Context->GetIsolate()); } + else if (IS_V8_ARRAY_BUFFER(v8ValueType)) { + if (IS_JAVA_INTEGER(jniEnv, mContext)) { + v8ValueValue = v8::ArrayBuffer::New(v8Context->GetIsolate(), TO_JAVA_INTEGER(jniEnv, mContext)); + } + } else if (IS_V8_FUNCTION(v8ValueType)) { jobject umContext = jniEnv->NewGlobalRef(mContext); Javet::Callback::JavetCallbackContextReference javetCallbackContextReference(jniEnv, umContext); @@ -309,6 +350,12 @@ JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_delete return false; } +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_equals +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle1, jlong v8ValueHandle2) { + RUNTIME_AND_2_VALUES_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle1, v8ValueHandle2); + return v8LocalObject1->Equals(v8Context, v8LocalObject2).FromMaybe(false); +} + JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_execute (JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jstring mScript, jboolean mReturnResult, jstring mResourceName, jint mResourceLineOffset, jint mResourceColumnOffset, jint mScriptId, jboolean mIsWASM, jboolean mIsModule) { @@ -342,7 +389,7 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_get RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); auto v8ValueKey = Javet::Converter::ToV8Value(jniEnv, v8Context, key); v8::Local v8ValueValue; - if (IS_V8_ARRAY(v8ValueType) || IS_V8_ARGUMENTS(v8ValueType)) { + if (IS_V8_ARGUMENTS(v8ValueType) || IS_V8_ARRAY(v8ValueType) || v8LocalObject->IsTypedArray()) { if (IS_JAVA_INTEGER(jniEnv, key)) { jint integerKey = TO_JAVA_INTEGER(jniEnv, key); if (integerKey >= 0) { @@ -382,11 +429,20 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_getGlobalObjec return Javet::Converter::ToExternalV8ValueGlobalObject(jniEnv, v8Runtime->v8GlobalObject); } +JNIEXPORT jint JNICALL Java_com_caoccao_javet_interop_V8Native_getIdentityHash +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType) { + RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); + return v8LocalObject->GetIdentityHash(); +} + JNIEXPORT jint JNICALL Java_com_caoccao_javet_interop_V8Native_getLength (JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType) { RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); if (IS_V8_ARRAY(v8ValueType)) { - return v8LocalObject.As()->Length(); + return (jint)v8LocalObject.As()->Length(); + } + if (v8LocalObject->IsTypedArray()) { + return (jint)v8LocalObject.As()->Length(); } return 0; } @@ -513,6 +569,18 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_invoke return nullptr; } +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_isDead +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle) { + auto v8Runtime = reinterpret_cast(v8RuntimeHandle); + return v8Runtime->v8Isolate->IsDead(); +} + +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_isInUse +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle) { + auto v8Runtime = reinterpret_cast(v8RuntimeHandle); + return v8Runtime->v8Isolate->IsInUse(); +} + JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_isWeak (JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType) { RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); @@ -670,6 +738,24 @@ JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_setWeak } } +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_sameValue +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle1, jlong v8ValueHandle2) { + RUNTIME_AND_2_VALUES_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle1, v8ValueHandle2); + return v8LocalObject1->SameValue(v8LocalObject2); +} + +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_strictEquals +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle1, jlong v8ValueHandle2) { + RUNTIME_AND_2_VALUES_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle1, v8ValueHandle2); + return v8LocalObject1->StrictEquals(v8LocalObject2); +} + +JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_terminateExecution +(JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle) { + auto v8Runtime = reinterpret_cast(v8RuntimeHandle); + v8Runtime->v8Isolate->TerminateExecution(); +} + JNIEXPORT jstring JNICALL Java_com_caoccao_javet_interop_V8Native_toProtoString (JNIEnv* jniEnv, jclass callerClass, jlong v8RuntimeHandle, jlong v8ValueHandle, jint v8ValueType) { RUNTIME_AND_VALUE_HANDLES_TO_OBJECTS_WITH_SCOPE(v8RuntimeHandle, v8ValueHandle); diff --git a/cpp/jni/com_caoccao_javet_interop_V8Native.h b/cpp/jni/com_caoccao_javet_interop_V8Native.h index 8a79de8e4..9313d542f 100644 --- a/cpp/jni/com_caoccao_javet_interop_V8Native.h +++ b/cpp/jni/com_caoccao_javet_interop_V8Native.h @@ -23,6 +23,14 @@ JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_add JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_call (JNIEnv *, jclass, jlong, jlong, jint, jobject, jboolean, jobjectArray); +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: callAsConstructor + * Signature: (JJI[Ljava/lang/Object;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_callAsConstructor + (JNIEnv *, jclass, jlong, jlong, jint, jobjectArray); + /* * Class: com_caoccao_javet_interop_V8Native * Method: clearWeak @@ -79,6 +87,14 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_createV8Value JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_delete (JNIEnv *, jclass, jlong, jlong, jint, jobject); +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: equals + * Signature: (JJJ)Z + */ +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_equals + (JNIEnv *, jclass, jlong, jlong, jlong); + /* * Class: com_caoccao_javet_interop_V8Native * Method: execute @@ -103,6 +119,14 @@ JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_get JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_getGlobalObject (JNIEnv *, jclass, jlong); +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: getIdentityHash + * Signature: (JJI)I + */ +JNIEXPORT jint JNICALL Java_com_caoccao_javet_interop_V8Native_getIdentityHash + (JNIEnv *, jclass, jlong, jlong, jint); + /* * Class: com_caoccao_javet_interop_V8Native * Method: getLength @@ -175,6 +199,22 @@ JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_hasOwnPropert JNIEXPORT jobject JNICALL Java_com_caoccao_javet_interop_V8Native_invoke (JNIEnv *, jclass, jlong, jlong, jint, jstring, jboolean, jobjectArray); +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: isDead + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_isDead + (JNIEnv *, jclass, jlong); + +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: isInUse + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_isInUse + (JNIEnv *, jclass, jlong); + /* * Class: com_caoccao_javet_interop_V8Native * Method: isWeak @@ -263,6 +303,30 @@ JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_setProperty JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_setWeak (JNIEnv *, jclass, jlong, jlong, jint, jobject); +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: sameValue + * Signature: (JJJ)Z + */ +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_sameValue + (JNIEnv *, jclass, jlong, jlong, jlong); + +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: strictEquals + * Signature: (JJJ)Z + */ +JNIEXPORT jboolean JNICALL Java_com_caoccao_javet_interop_V8Native_strictEquals + (JNIEnv *, jclass, jlong, jlong, jlong); + +/* + * Class: com_caoccao_javet_interop_V8Native + * Method: terminateExecution + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_caoccao_javet_interop_V8Native_terminateExecution + (JNIEnv *, jclass, jlong); + /* * Class: com_caoccao_javet_interop_V8Native * Method: toProtoString diff --git a/cpp/jni/javet.rc b/cpp/jni/javet.rc index 1f70f4d61..a2e96d2e6 100644 --- a/cpp/jni/javet.rc +++ b/cpp/jni/javet.rc @@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,7,1,0 - PRODUCTVERSION 0,7,1,0 + FILEVERSION 0,7,2,0 + PRODUCTVERSION 0,7,2,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -79,12 +79,12 @@ BEGIN BEGIN VALUE "CompanyName", "caoccao.com" VALUE "FileDescription", "caoccao.com" - VALUE "FileVersion", "0.7.1.0" - VALUE "InternalName", "javet-windows-x86_64.v.0.7.1.dll" + VALUE "FileVersion", "0.7.2.0" + VALUE "InternalName", "javet-windows-x86_64.v.0.7.2.dll" VALUE "LegalCopyright", "Copyright (C) 2021" - VALUE "OriginalFilename", "javet-windows-x86_64.v.0.7.1.dll" + VALUE "OriginalFilename", "javet-windows-x86_64.v.0.7.2.dll" VALUE "ProductName", "Javet Windows" - VALUE "ProductVersion", "0.7.1.0" + VALUE "ProductVersion", "0.7.2.0" END END BLOCK "VarFileInfo" diff --git a/cpp/jni/javet_converter.cpp b/cpp/jni/javet_converter.cpp index 0f2795551..e2b961f1f 100644 --- a/cpp/jni/javet_converter.cpp +++ b/cpp/jni/javet_converter.cpp @@ -16,6 +16,7 @@ */ #include "javet_converter.h" +#include "javet_enums.h" // Primitive #define IS_JAVA_BOOLEAN(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueBoolean) @@ -30,10 +31,13 @@ // Reference #define IS_JAVA_ARGUMENTS(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueArguments) #define IS_JAVA_ARRAY(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueArray) +#define IS_JAVA_ARRAY_BUFFER(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueArrayBuffer) +#define IS_JAVA_DATA_VIEW(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueDataView) #define IS_JAVA_FUNCTION(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueFunction) #define IS_JAVA_ERROR(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueError) #define IS_JAVA_GLOBAL_OBJECT(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueGlobalObject) #define IS_JAVA_MAP(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueMap) +#define IS_JAVA_ITERATOR(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueIterator) #define IS_JAVA_OBJECT(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueObject) #define IS_JAVA_PROMISE(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValuePromise) #define IS_JAVA_PROXY(jniEnv, obj) jniEnv->IsInstanceOf(obj, jclassV8ValueProxy) @@ -96,6 +100,14 @@ namespace Javet { jmethodIDV8ValueArrayConstructor = jniEnv->GetMethodID(jclassV8ValueArray, "", "(J)V"); jmethodIDV8ValueArrayGetHandle = jniEnv->GetMethodID(jclassV8ValueArray, "getHandle", "()J"); + jclassV8ValueArrayBuffer = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueArrayBuffer")); + jmethodIDV8ValueArrayBufferConstructor = jniEnv->GetMethodID(jclassV8ValueArrayBuffer, "", "(JLjava/nio/ByteBuffer;)V"); + jmethodIDV8ValueArrayBufferGetHandle = jniEnv->GetMethodID(jclassV8ValueArrayBuffer, "getHandle", "()J"); + + jclassV8ValueDataView = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueDataView")); + jmethodIDV8ValueDataViewConstructor = jniEnv->GetMethodID(jclassV8ValueDataView, "", "(J)V"); + jmethodIDV8ValueDataViewGetHandle = jniEnv->GetMethodID(jclassV8ValueDataView, "getHandle", "()J"); + jclassV8ValueFunction = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueFunction")); jmethodIDV8ValueFunctionConstructor = jniEnv->GetMethodID(jclassV8ValueFunction, "", "(J)V"); jmethodIDV8ValueFunctionGetHandle = jniEnv->GetMethodID(jclassV8ValueFunction, "getHandle", "()J"); @@ -112,6 +124,10 @@ namespace Javet { jmethodIDV8ValueMapConstructor = jniEnv->GetMethodID(jclassV8ValueMap, "", "(J)V"); jmethodIDV8ValueMapGetHandle = jniEnv->GetMethodID(jclassV8ValueMap, "getHandle", "()J"); + jclassV8ValueIterator = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueIterator")); + jmethodIDV8ValueIteratorConstructor = jniEnv->GetMethodID(jclassV8ValueIterator, "", "(J)V"); + jmethodIDV8ValueIteratorGetHandle = jniEnv->GetMethodID(jclassV8ValueIterator, "getHandle", "()J"); + jclassV8ValueObject = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueObject")); jmethodIDV8ValueObjectConstructor = jniEnv->GetMethodID(jclassV8ValueObject, "", "(J)V"); jmethodIDV8ValueObjectGetHandle = jniEnv->GetMethodID(jclassV8ValueObject, "getHandle", "()J"); @@ -137,6 +153,10 @@ namespace Javet { jclassV8ValueSymbol = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueSymbol")); jmethodIDV8ValueSymbolConstructor = jniEnv->GetMethodID(jclassV8ValueSymbol, "", "(J)V"); jmethodIDV8ValueSymbolGetHandle = jniEnv->GetMethodID(jclassV8ValueSymbol, "getHandle", "()J"); + + jclassV8ValueTypedArray = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/values/reference/V8ValueTypedArray")); + jmethodIDV8ValueTypedArrayConstructor = jniEnv->GetMethodID(jclassV8ValueTypedArray, "", "(JI)V"); + jmethodIDV8ValueTypedArrayGetHandle = jniEnv->GetMethodID(jclassV8ValueTypedArray, "getHandle", "()J"); } jobject ToExternalV8ValueArray(JNIEnv* jniEnv, v8::Local& v8Context, const v8::FunctionCallbackInfo& args) { @@ -165,47 +185,66 @@ namespace Javet { return jniEnv->NewObject(jclassV8ValueArray, jmethodIDV8ValueArrayConstructor, ToV8PersistentObjectReference(v8Context, v8Value)); } if (v8Value->IsTypedArray()) { - // TODO + int type = Javet::Enums::V8ValueReferenceType::Invalid; if (v8Value->IsBigInt64Array()) { + type = Javet::Enums::V8ValueReferenceType::BigInt64Array; } - if (v8Value->IsBigUint64Array()) { + else if (v8Value->IsBigUint64Array()) { + type = Javet::Enums::V8ValueReferenceType::BigUint64Array; } - if (v8Value->IsFloat32Array()) { + else if (v8Value->IsFloat32Array()) { + type = Javet::Enums::V8ValueReferenceType::Float32Array; } - if (v8Value->IsFloat64Array()) { + else if (v8Value->IsFloat64Array()) { + type = Javet::Enums::V8ValueReferenceType::Float64Array; } - if (v8Value->IsInt16Array()) { + else if (v8Value->IsInt16Array()) { + type = Javet::Enums::V8ValueReferenceType::Int16Array; } - if (v8Value->IsInt32Array()) { + else if (v8Value->IsInt32Array()) { + type = Javet::Enums::V8ValueReferenceType::Int32Array; } - if (v8Value->IsInt8Array()) { + else if (v8Value->IsInt8Array()) { + type = Javet::Enums::V8ValueReferenceType::Int8Array; } - if (v8Value->IsUint16Array()) { + else if (v8Value->IsUint16Array()) { + type = Javet::Enums::V8ValueReferenceType::Uint16Array; } - if (v8Value->IsUint32Array()) { + else if (v8Value->IsUint32Array()) { + type = Javet::Enums::V8ValueReferenceType::Uint32Array; } - if (v8Value->IsUint8Array()) { + else if (v8Value->IsUint8Array()) { + type = Javet::Enums::V8ValueReferenceType::Uint8Array; } - if (v8Value->IsUint8ClampedArray()) { + else if (v8Value->IsUint8ClampedArray()) { + type = Javet::Enums::V8ValueReferenceType::Uint8ClampedArray; } + if (type != Javet::Enums::V8ValueReferenceType::Invalid) { + return jniEnv->NewObject(jclassV8ValueTypedArray, jmethodIDV8ValueTypedArrayConstructor, ToV8PersistentObjectReference(v8Context, v8Value), type); + } + } + if (v8Value->IsDataView()) { + return jniEnv->NewObject(jclassV8ValueDataView, jmethodIDV8ValueDataViewConstructor, ToV8PersistentObjectReference(v8Context, v8Value)); } if (v8Value->IsArrayBuffer()) { - // TODO + auto v8ArrayBuffer = v8Value.As(); + return jniEnv->NewObject(jclassV8ValueArrayBuffer, jmethodIDV8ValueArrayBufferConstructor, ToV8PersistentObjectReference(v8Context, v8Value), + jniEnv->NewDirectByteBuffer(v8ArrayBuffer->GetBackingStore()->Data(), v8ArrayBuffer->ByteLength())); } if (v8Value->IsArrayBufferView()) { - // TODO + /* + ArrayBufferView is a helper type representing any of typed array or DataView. + This block shouldn't be entered. + */ } if (v8Value->IsMap()) { return jniEnv->NewObject(jclassV8ValueMap, jmethodIDV8ValueMapConstructor, ToV8PersistentObjectReference(v8Context, v8Value)); } - if (v8Value->IsMapIterator()) { - // It defaults to V8ValueObject. - } if (v8Value->IsSet()) { return jniEnv->NewObject(jclassV8ValueSet, jmethodIDV8ValueSetConstructor, ToV8PersistentObjectReference(v8Context, v8Value)); } - if (v8Value->IsSetIterator()) { - // It defaults to V8ValueObject. + if (v8Value->IsMapIterator() || v8Value->IsSetIterator()) { + return jniEnv->NewObject(jclassV8ValueIterator, jmethodIDV8ValueIteratorConstructor, ToV8PersistentObjectReference(v8Context, v8Value)); } if (v8Value->IsWeakMap()) { // TODO @@ -252,18 +291,8 @@ namespace Javet { return jniEnv->NewObject(jclassV8ValueInteger, jmethodIDV8ValueIntegerConstructor, v8Value->Int32Value(v8Context).FromMaybe(0)); } if (v8Value->IsBigInt() || v8Value->IsBigIntObject()) { -#ifdef JAVET_CONVERTER_BIGINT_STANDARD - // This is the standard way of getting int64. return jniEnv->NewObject(jclassV8ValueLong, jmethodIDV8ValueLongConstructorFromLong, v8Value->ToBigInt(v8Context).ToLocalChecked()->Int64Value()); -#else - // There is another way of getting int64. This branch is disabled by default. - v8::String::Value stringValue(v8Context->GetIsolate(), v8Value); - jstring mStringValue = jniEnv->NewString(*stringValue, stringValue.length()); - jobject mV8Value = jniEnv->NewObject(jclassV8ValueLong, jmethodIDV8ValueLongConstructorFromString, mStringValue); - jniEnv->DeleteLocalRef(mStringValue); - return mV8Value; -#endif // JAVET_CONVERTER_BIGINT_STANDARD } if (v8Value->IsDate()) { auto v8Date = v8Value->ToObject(v8Context).ToLocalChecked().As(); @@ -424,9 +453,9 @@ namespace Javet { return v8::Local::New(v8Context->GetIsolate(), *reinterpret_cast*>( jniEnv->CallLongMethod(obj, jmethodIDV8ValueMapGetHandle))); } - else if (IS_JAVA_OBJECT(jniEnv, obj)) { + else if (IS_JAVA_ITERATOR(jniEnv, obj)) { return v8::Local::New(v8Context->GetIsolate(), *reinterpret_cast*>( - jniEnv->CallLongMethod(obj, jmethodIDV8ValueObjectGetHandle))); + jniEnv->CallLongMethod(obj, jmethodIDV8ValueIteratorGetHandle))); } else if (IS_JAVA_PROMISE(jniEnv, obj)) { return v8::Local::New(v8Context->GetIsolate(), *reinterpret_cast*>( @@ -448,6 +477,10 @@ namespace Javet { return v8::Local::New(v8Context->GetIsolate(), *reinterpret_cast*>( jniEnv->CallLongMethod(obj, jmethodIDV8ValueSymbolGetHandle))); } + else if (IS_JAVA_OBJECT(jniEnv, obj)) { + return v8::Local::New(v8Context->GetIsolate(), *reinterpret_cast*>( + jniEnv->CallLongMethod(obj, jmethodIDV8ValueObjectGetHandle))); + } } return ToV8Undefined(v8Context); } diff --git a/cpp/jni/javet_converter.h b/cpp/jni/javet_converter.h index 9bbe848b9..ad32f37e5 100644 --- a/cpp/jni/javet_converter.h +++ b/cpp/jni/javet_converter.h @@ -21,8 +21,6 @@ #include #include -#define JAVET_CONVERTER_BIGINT_STANDARD - namespace Javet { namespace Converter { // Primitive @@ -71,6 +69,14 @@ namespace Javet { static jmethodID jmethodIDV8ValueArrayConstructor; static jmethodID jmethodIDV8ValueArrayGetHandle; + static jclass jclassV8ValueArrayBuffer; + static jmethodID jmethodIDV8ValueArrayBufferConstructor; + static jmethodID jmethodIDV8ValueArrayBufferGetHandle; + + static jclass jclassV8ValueDataView; + static jmethodID jmethodIDV8ValueDataViewConstructor; + static jmethodID jmethodIDV8ValueDataViewGetHandle; + static jclass jclassV8ValueFunction; static jmethodID jmethodIDV8ValueFunctionConstructor; static jmethodID jmethodIDV8ValueFunctionGetHandle; @@ -87,6 +93,10 @@ namespace Javet { static jmethodID jmethodIDV8ValueMapConstructor; static jmethodID jmethodIDV8ValueMapGetHandle; + static jclass jclassV8ValueIterator; + static jmethodID jmethodIDV8ValueIteratorConstructor; + static jmethodID jmethodIDV8ValueIteratorGetHandle; + static jclass jclassV8ValueObject; static jmethodID jmethodIDV8ValueObjectConstructor; static jmethodID jmethodIDV8ValueObjectGetHandle; @@ -113,6 +123,10 @@ namespace Javet { static jmethodID jmethodIDV8ValueSymbolConstructor; static jmethodID jmethodIDV8ValueSymbolGetHandle; + static jclass jclassV8ValueTypedArray; + static jmethodID jmethodIDV8ValueTypedArrayConstructor; + static jmethodID jmethodIDV8ValueTypedArrayGetHandle; + void Initialize(JNIEnv* jniEnv); jobject ToExternalV8Value(JNIEnv* jniEnv, v8::Local& v8Context, v8::Local v8Value); diff --git a/cpp/jni/javet_enums.h b/cpp/jni/javet_enums.h index 001853c44..19380c1d4 100644 --- a/cpp/jni/javet_enums.h +++ b/cpp/jni/javet_enums.h @@ -2,7 +2,7 @@ * Copyright (c) 2021 caoccao.com Sam Cao * All rights reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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 @@ -18,19 +18,34 @@ #pragma once namespace Javet { - namespace Enums { - enum V8ValueReferenceType { - Object = 1, - Error = 2, - RegExp = 3, - Promise = 4, - Proxy = 5, - Symbol = 6, - Arguments = 7, - Map = 8, - Set = 9, - Array = 10, - Function = 11, - }; - } + namespace Enums { + enum V8ValueReferenceType { + Invalid = 0, + Object = 1, + Error = 2, + RegExp = 3, + Promise = 4, + Proxy = 5, + Symbol = 6, + Arguments = 7, + Map = 8, + Set = 9, + Array = 10, + Function = 11, + Iterator = 12, + DataView = 30, + ArrayBuffer = 31, + Int8Array = 32, // -128 to 127 1 8-bit two's complement signed integer byte int8_t + Uint8Array = 33, // 0 to 255 1 8-bit unsigned integer octet uint8_t + Uint8ClampedArray = 34, // 0 to 255 1 8-bit unsigned integer (clamped) octet uint8_t + Int16Array = 35, // -32768 to 32767 2 16-bit two's complement signed integer short int16_t + Uint16Array = 36, // 0 to 65535 2 16-bit unsigned integer unsigned short uint16_t + Int32Array = 37, // -2147483648 to 2147483647 4 32-bit two's complement signed integer long int32_t + Uint32Array = 38, // 0 to 4294967295 4 32-bit unsigned integer unsigned long uint32_t + Float32Array = 39, // 1.2¡Á10^-38 to 3.4¡Á10^38 4 32-bit IEEE floating point number (7 significant digits e.g., 1.234567) unrestricted float float + Float64Array = 40, // 5.0¡Á10^-324 to 1.8¡Á10^308 8 64-bit IEEE floating point number (16 significant digits e.g., 1.23456789012345) unrestricted double double + BigInt64Array = 41, // -2^63 to 2^63-1 8 64-bit two's complement signed integer bigint int64_t (signed long long) + BigUint64Array = 42, // 0 to 2^64-1 8 64-bit unsigned integer bigint uint64_t (unsigned long long) + }; + } } \ No newline at end of file diff --git a/cpp/jni/javet_exceptions.cpp b/cpp/jni/javet_exceptions.cpp index 70b64947f..0e6750f5c 100644 --- a/cpp/jni/javet_exceptions.cpp +++ b/cpp/jni/javet_exceptions.cpp @@ -31,6 +31,8 @@ namespace Javet { jclassJavetConverterException = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/exceptions/JavetConverterException")); jclassJavetExecutionException = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/exceptions/JavetExecutionException")); jmethodIDJavetExecutionExceptionConstructor = jniEnv->GetMethodID(jclassJavetExecutionException, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIII)V"); + jclassJavetTerminatedException = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/exceptions/JavetTerminatedException")); + jmethodIDJavetTerminatedExceptionConstructor = jniEnv->GetMethodID(jclassJavetTerminatedException, "", "(Z)V"); jclassJavetUnknownCompilationException = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/exceptions/JavetUnknownCompilationException")); jmethodIDJavetUnknownCompilationExceptionConstructor = jniEnv->GetMethodID(jclassJavetUnknownCompilationException, "", "(Ljava/lang/String;)V"); jclassJavetUnknownExecutionException = (jclass)jniEnv->NewGlobalRef(jniEnv->FindClass("com/caoccao/javet/exceptions/JavetUnknownExecutionException")); @@ -42,8 +44,8 @@ namespace Javet { auto isolate = v8Context->GetIsolate(); v8::String::Value exceptionMessage(isolate, v8TryCatch.Exception()); jstring jStringExceptionMessage = jniEnv->NewString(*exceptionMessage, exceptionMessage.length()); - auto compileErrorMessage = v8TryCatch.Message(); - if (compileErrorMessage.IsEmpty()) { + auto v8LocalMessage = v8TryCatch.Message(); + if (v8LocalMessage.IsEmpty()) { jthrowable javetUnknownCompilationException = (jthrowable)jniEnv->NewObject( jclassJavetUnknownCompilationException, jmethodIDJavetUnknownCompilationExceptionConstructor, @@ -51,9 +53,9 @@ namespace Javet { jniEnv->Throw(javetUnknownCompilationException); } else { - v8::String::Utf8Value scriptResourceName(isolate, compileErrorMessage->GetScriptResourceName()); + v8::String::Utf8Value scriptResourceName(isolate, v8LocalMessage->GetScriptResourceName()); jstring jStringScriptResourceName = jniEnv->NewStringUTF(*scriptResourceName); - v8::String::Value sourceLine(isolate, compileErrorMessage->GetSourceLine(v8Context).ToLocalChecked()); + v8::String::Value sourceLine(isolate, v8LocalMessage->GetSourceLine(v8Context).ToLocalChecked()); jstring jStringSourceLine = jniEnv->NewString(*sourceLine, sourceLine.length()); jthrowable javetConverterException = (jthrowable)jniEnv->NewObject( jclassJavetCompilationException, @@ -61,11 +63,11 @@ namespace Javet { jStringExceptionMessage, jStringScriptResourceName, jStringSourceLine, - compileErrorMessage->GetLineNumber(v8Context).FromMaybe(0), - compileErrorMessage->GetStartColumn(), - compileErrorMessage->GetEndColumn(), - compileErrorMessage->GetStartPosition(), - compileErrorMessage->GetEndPosition() ); + v8LocalMessage->GetLineNumber(v8Context).FromMaybe(0), + v8LocalMessage->GetStartColumn(), + v8LocalMessage->GetEndColumn(), + v8LocalMessage->GetStartPosition(), + v8LocalMessage->GetEndPosition()); jniEnv->Throw(javetConverterException); jniEnv->DeleteLocalRef(jStringSourceLine); jniEnv->DeleteLocalRef(jStringScriptResourceName); @@ -79,37 +81,46 @@ namespace Javet { void ThrowJavetExecutionException(JNIEnv* jniEnv, const v8::Local& v8Context, const v8::TryCatch& v8TryCatch) { auto isolate = v8Context->GetIsolate(); - v8::String::Value exceptionMessage(isolate, v8TryCatch.Exception()); - jstring jStringExceptionMessage = jniEnv->NewString(*exceptionMessage, exceptionMessage.length()); - auto compileErrorMessage = v8TryCatch.Message(); - if (compileErrorMessage.IsEmpty()) { - jthrowable javetUnknownExecutionException = (jthrowable)jniEnv->NewObject( - jclassJavetUnknownExecutionException, - jmethodIDJavetUnknownExecutionExceptionConstructor, - jStringExceptionMessage); - jniEnv->Throw(javetUnknownExecutionException); + if (v8TryCatch.HasTerminated()) { + jthrowable javetTerminatedException = (jthrowable)jniEnv->NewObject( + jclassJavetTerminatedException, + jmethodIDJavetTerminatedExceptionConstructor, + v8TryCatch.CanContinue()); + jniEnv->Throw(javetTerminatedException); } else { - v8::String::Utf8Value scriptResourceName(isolate, compileErrorMessage->GetScriptResourceName()); - jstring jStringScriptResourceName = jniEnv->NewStringUTF(*scriptResourceName); - v8::String::Value sourceLine(isolate, compileErrorMessage->GetSourceLine(v8Context).ToLocalChecked()); - jstring jStringSourceLine = jniEnv->NewString(*sourceLine, sourceLine.length()); - jthrowable javetConverterException = (jthrowable)jniEnv->NewObject( - jclassJavetExecutionException, - jmethodIDJavetExecutionExceptionConstructor, - jStringExceptionMessage, - jStringScriptResourceName, - jStringSourceLine, - compileErrorMessage->GetLineNumber(v8Context).FromMaybe(0), - compileErrorMessage->GetStartColumn(), - compileErrorMessage->GetEndColumn(), - compileErrorMessage->GetStartPosition(), - compileErrorMessage->GetEndPosition() ); - jniEnv->Throw(javetConverterException); - jniEnv->DeleteLocalRef(jStringSourceLine); - jniEnv->DeleteLocalRef(jStringScriptResourceName); + v8::String::Value exceptionMessage(isolate, v8TryCatch.Exception()); + jstring jStringExceptionMessage = jniEnv->NewString(*exceptionMessage, exceptionMessage.length()); + auto v8LocalMessage = v8TryCatch.Message(); + if (v8LocalMessage.IsEmpty()) { + jthrowable javetUnknownExecutionException = (jthrowable)jniEnv->NewObject( + jclassJavetUnknownExecutionException, + jmethodIDJavetUnknownExecutionExceptionConstructor, + jStringExceptionMessage); + jniEnv->Throw(javetUnknownExecutionException); + } + else { + v8::String::Utf8Value scriptResourceName(isolate, v8LocalMessage->GetScriptResourceName()); + jstring jStringScriptResourceName = jniEnv->NewStringUTF(*scriptResourceName); + v8::String::Value sourceLine(isolate, v8LocalMessage->GetSourceLine(v8Context).ToLocalChecked()); + jstring jStringSourceLine = jniEnv->NewString(*sourceLine, sourceLine.length()); + jthrowable javetConverterException = (jthrowable)jniEnv->NewObject( + jclassJavetExecutionException, + jmethodIDJavetExecutionExceptionConstructor, + jStringExceptionMessage, + jStringScriptResourceName, + jStringSourceLine, + v8LocalMessage->GetLineNumber(v8Context).FromMaybe(0), + v8LocalMessage->GetStartColumn(), + v8LocalMessage->GetEndColumn(), + v8LocalMessage->GetStartPosition(), + v8LocalMessage->GetEndPosition()); + jniEnv->Throw(javetConverterException); + jniEnv->DeleteLocalRef(jStringSourceLine); + jniEnv->DeleteLocalRef(jStringScriptResourceName); + } + jniEnv->DeleteLocalRef(jStringExceptionMessage); } - jniEnv->DeleteLocalRef(jStringExceptionMessage); } void ThrowJavetV8RuntimeLockConflictException(JNIEnv* jniEnv, const char* message) { diff --git a/cpp/jni/javet_exceptions.h b/cpp/jni/javet_exceptions.h index f1cab06cd..767b8bf52 100644 --- a/cpp/jni/javet_exceptions.h +++ b/cpp/jni/javet_exceptions.h @@ -27,6 +27,8 @@ namespace Javet { static jclass jclassJavetConverterException; static jclass jclassJavetExecutionException; static jmethodID jmethodIDJavetExecutionExceptionConstructor; + static jclass jclassJavetTerminatedException; + static jmethodID jmethodIDJavetTerminatedExceptionConstructor; static jclass jclassJavetUnknownCompilationException; static jmethodID jmethodIDJavetUnknownCompilationExceptionConstructor; static jclass jclassJavetUnknownExecutionException; diff --git a/docs/development/best_practices.rst b/docs/development/best_practices.rst new file mode 100644 index 000000000..b9a57e2ca --- /dev/null +++ b/docs/development/best_practices.rst @@ -0,0 +1,28 @@ +============== +Best Practices +============== + +Thread, Engine and Pool +======================= + +* Always get 1 Javet engine from the pool in 1 thread. +* If multiple context is required in 1 thread, there are 2 options. + * Call ``resetContext()`` between context switch. + * Obtain multiple V8Runtime instances. +* Do not pass Javet engine to other threads. +* Always return Javet engine to pool in the end via try-with-resource or calling ``close()`` explicitly. +* Subclass Javet engine pool and Javet engine to complete your customization. Indeed, they are open to full customization. + +Resource Management +=================== + +* Dangling V8 objects will be forced to be recycled by Javet under the following scenarios and corresponding log will reflect that. Keeping an eye on the log helps address memory leak issues in the early stage. + * Engine is closed. + * Pool is closed. + * Context is reset. + * Isolate is reset. +* Always apply try-with-resource to Javet objects regardless of primitive or reference if they are not returned to Javet. +* Always prohibit calling ``close()`` of Javet objects if they will be returned to Javet. +* If the lifecycle of V8 objects is uncertain, calling ``setWeak()`` is the only way so that calling ``close()`` is no longer required. Be careful, calling ``close()`` after calling ``setWeak()`` may lead to V8 core dump immediately. + +[`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/build.rst b/docs/development/build.rst similarity index 97% rename from docs/build.rst rename to docs/development/build.rst index 8f3d866a1..3d610bb24 100644 --- a/docs/build.rst +++ b/docs/development/build.rst @@ -89,4 +89,4 @@ Also, please make sure ``args.gn`` file looks like the following. ``v8_monolith`` is the build target. -[`Home <../README.rst>`_] +[`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/development/design.rst b/docs/development/design.rst new file mode 100644 index 000000000..4620f25c8 --- /dev/null +++ b/docs/development/design.rst @@ -0,0 +1,58 @@ +============ +Javet Design +============ + +Architecture +============ + +.. image:: ../resources/images/javet_architecture.png?raw=true + :alt: Javet Architecture + +Primitive and Reference Types in Javet +-------------------------------------- + +There is a vague boundary between V8 primitive and reference types. In Javet, the definition of primitive is a mixture of both V8 and Java primitive types as a trade-off in design. + +=========================== ======================= ============================== +Feature Primitive Reference +=========================== ======================= ============================== +Interception No Yes +Memory Copy Copy by Value Copy by Reference +Resource Leak Not Possible Possible +Set to Weak No Yes +=========================== ======================= ============================== + +Reference typed objects keep memory footprint in V8 + JNI + JVM. All resource will be recycled when ``close()`` is called. That is quite an old school way of managing resource. Javet tries to hide that kind of tedious work from Java applications via try-with-resource. + +Please refer to `Best Practices `_ for detail. + +Engine Pool +=========== + +.. image:: ../resources/images/javet_engine_pool.png?raw=true + :alt: Javet Engine Pool + +V8 Isolate and Context in Javet +------------------------------- + +`Getting started with embedding V8 `_ is an excellent article that explains the concepts, design, insights of V8. In summary: + +* An isolate is a VM instance with its own heap. +* A context is an execution environment that allows separate, unrelated, JavaScript applications to run in a single instance of V8. + +In Javet, that model is simplified to 1 engine - 1 runtime - 1 isolate - 1 context. In V8Runtime, ``resetIsolate()`` and ``resetContext()`` are both exposed. It is recommended to always use ``resetContext()`` to get a brand new V8 context for the following reasons. + +* ``resetContext()`` is a much cheaper operation with much better performance. +* ``resetContext()`` is good enough in terms of getting a brand new V8 context. + +Javet Engine Pool +----------------- + +Multiple Javet engines are managed by Javet Engine Pool which works almost the same way as a typical DB connection pool. Javet Engine Pool is thread-safe. However, Javet Engine is **NOT** thread-safe because it is designed to be single-threaded and lock free for the following reasons. + +* V8 isolate and V8 context are single-threaded. Thread context violation results in V8 core dump immediately. +* Javet Engine performs better without locks. Actually, Javet engine only validates current thread ID to minimize the performance overhead. + +Please refer to `Best Practices `_ for detail. + +[`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/development/index.rst b/docs/development/index.rst new file mode 100644 index 000000000..28869a82f --- /dev/null +++ b/docs/development/index.rst @@ -0,0 +1,11 @@ +=========== +Development +=========== + +* `Development Tools `_ +* `Build `_ +* `Design `_ +* `Best Practices `_ +* `Performance `_ + +[`Home <../../README.rst>`_] diff --git a/docs/performance.rst b/docs/development/performance.rst similarity index 73% rename from docs/performance.rst rename to docs/development/performance.rst index bf8624ec3..816904c8f 100644 --- a/docs/performance.rst +++ b/docs/development/performance.rst @@ -11,12 +11,12 @@ Here is the performance test result from i7 10700K + Windows 10. Test case is ju =============================== =============== Case TPS =============================== =============== -Single Context with 1 Thread 759,589 -Ad-hoc Context with 1 Thread 2,921 -Single Context with 8 Threads 4,464,285 -Ad-hoc Context with 8 Threads 15,209 +Single Context with 1 Thread 752,728 +Ad-hoc Context with 1 Thread 2,895 +Single Context with 8 Threads 4,268,943 +Ad-hoc Context with 8 Threads 15,278 =============================== =============== Reference: https://v8.dev/docs/embed#contexts -[`Home <../README.rst>`_] +[`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/development.rst b/docs/development/tools.rst similarity index 65% rename from docs/development.rst rename to docs/development/tools.rst index 49776a008..58f950747 100644 --- a/docs/development.rst +++ b/docs/development/tools.rst @@ -1,38 +1,40 @@ ================= -Javet Development -================= - Development Tools ================= JDK ---- +=== Javet development requires JDK 8 to be installed, though Javet supports JDK 8+. JDK 6 support has been dropped because a few JDK 8 features are heavily used in Javet. -IDE ---- +IntelliJ +======== I personally recommend IntelliJ IDEA. Gradle ------- +====== For now, Gradle v6.7 + Kotlin DSL constructs the build system. -Maven ------ +Maven (Optional) +================ + +Maven v3.6.3+ is used for packaging bundle to maven central. -Maven v3.6.3+ is the one. +Visual Studio Community 2019 (Optional) +======================================= + +Visual Studio Community 2019 is used for JNI development. Python 3.8+ (Optional) ----------------------- +====================== Python 3.8+ is required if you want to upgrade the version. NodeJS (Optional) ------------------ +================= NodeJS 14.5+ is required if you want to go over the tutorial because a few examples use some node modules. After installing NodeJS, please visit ``scripts/node`` directory and run ``npm install``. -[`Home <../README.rst>`_] +[`Home <../../README.rst>`_] [`Development `_] diff --git a/docs/faq/history_with_j2v8.rst b/docs/faq/history_with_j2v8.rst index a7d0e9516..9d6161d03 100644 --- a/docs/faq/history_with_j2v8.rst +++ b/docs/faq/history_with_j2v8.rst @@ -1,3 +1,4 @@ +================= History with J2V8 ================= diff --git a/docs/faq/index.rst b/docs/faq/index.rst index d010528a6..727c78929 100644 --- a/docs/faq/index.rst +++ b/docs/faq/index.rst @@ -3,5 +3,6 @@ FAQ === * `History with J2V8 `_ +* `What is the Motivation? `_ [`Home <../../README.rst>`_] diff --git a/docs/faq/what_is_the_motivation.rst b/docs/faq/what_is_the_motivation.rst new file mode 100644 index 000000000..57490579b --- /dev/null +++ b/docs/faq/what_is_the_motivation.rst @@ -0,0 +1,13 @@ +======================= +What is the Motivation? +======================= + +I used to take a try of J2V8 and find it's quite compelling. However, J2V8 is slowly dying, with serious memory leak issues, V8 version issue, etc. + +Sometimes starting from scratch implies lower cost than upgrading an existing solution. I think it might be true here in this project. I've learned quite a lot by manually fixing the Windows and Linux build system of J2V8. + +Also, I had got many ideas on how the API will look like. At the end of 2020, I thought I would be able to write a new one from scratch and leave J2V8 behind. Indeed, I made it few months later. + +Please refer to `History with J2V8 `_ for detail. + +[`Home <../../README.rst>`_] [`FAQ `_] diff --git a/docs/release_notes.rst b/docs/release_notes.rst index ccd612c25..df8a86609 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -2,6 +2,16 @@ Release Notes ============= +0.7.2 +----- + +* Added ``setFunction(String functionName, String codeString)`` to ``IV8ValueObject`` +* Added ``equals()`` and ``strictEquals()`` and ``sameValue()`` to ``IV8Value`` +* Added ``getIdentityHash()`` to ``IV8ValueReference`` +* Added ``isDead()``, ``isInUse()``, ``callAsConstructor()`` and ``terminateExecution()`` to ``V8Runtime`` +* Added V8 typed array and data view +* Added ``IJavetEngineGuard`` + 0.7.1 ----- diff --git a/docs/resources/images/javet_architecture.drawio b/docs/resources/images/javet_architecture.drawio new file mode 100644 index 000000000..128975c74 --- /dev/null +++ b/docs/resources/images/javet_architecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/resources/images/javet_architecture.png b/docs/resources/images/javet_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..295eeac6cd4481b542870137c09de538be545462 GIT binary patch literal 156650 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 literal 0 HcmV?d00001 diff --git a/docs/resources/images/javet_engine_pool.drawio b/docs/resources/images/javet_engine_pool.drawio new file mode 100644 index 000000000..c9e98852e --- /dev/null +++ b/docs/resources/images/javet_engine_pool.drawio @@ -0,0 +1 @@ +7VpZc5s6FP41fkyH3fCYOGm63KW9mdvbPCpIBqUy8sjy1l9/JSPMImHHtcHNOMlMgo4WxPed83E4MHBHk9U9A9P0TwoRGTgWXA3c24Hj2J4biH/SslYWy1aWhGGobKXhAf9ExUBlnWOIZrWBnFLC8bRujGmWoZjXbIAxuqwPG1NSP+sUJEgzPMSA6Nb/MORpbg19q7R/QDhJizPbluqZgGKwMsxSAOmyYnLvBu6IUcrzo8lqhIhEr8Aln/e+pXe7MYYy/pIJ0fOHv77S9FPwc+ZB/Agen937qzBfZQHIXF2w2ixfFwgwOs8gkotYA/dmmWKOHqYglr1LQbqwpXxCRMsWh2OacUWiLYfrm1T7XiDG0apiUpu+R3SCOFuLIarXVfgtS/jdobKlFeidwggU5cl2qRIVcaCAOQCkSAPpE1ggLkx3WYIzJA6+CJ/UkBMXyOvwzDijP9CIEsqEJaNirsAME9IwAYKTTDRjARsS9hsJFxZeea06JhhCeRojH3XGNpSoTTUpCk5DkaeQ3wa5zphvYqwrwobOIW5tv8CtKxQNHHc8RkEca3yKHjiMnqwTOX4DVU9HdSs2NVS7g9XVYP0WvgqvP56MYZ0MXyfD69XDAw13BMWNSzUp4ylNaAbIXWltIFSO+YPSqaLqGXG+VuoA5pzWiUQrzL/L6e981Xqs9Nyu1MqbxrpoZOJ6v1cbj+UKsllO27TKefBa3r9L9xCW91jCVBJcudPIdiUQvVv5uysQZ3TOYrQLY0/lG4AliO8a6OcDJQM7fYkhAjhe1FMLk29sporLB+vKgCnFGZ9VVv4iDa0uatfzAXGQL1g63XZnR/ihp0nCxxkVV4mOFNwGu5o0aIocQyfcqcgJAxCjmpOMx8A/lVr7dfTDcwuErxEzEphKbe6XGAAj6JuIAcizXec04EcN1zdkIP2ir98W+5TnijiXUt0mz2eX2WEn6rlXHq0G+bnMq0kl/8eqsN+HCg8vOdgNiXG/wR695WJdi0RxO92bi4X275iLFY/EnapAgdFbLtbIxezhmRWicMqL1GfHOjf6evHnLRlrw8o9TzIW9ZKMFX7XrQzrVbILCnZDtb6rYL8Zsmj1+fPXh8Vy9O9HmPwNn+MrXWmPRL0OKvRRCD0TqKHz5AZlPV1D0IBzK6h24wZmKv3aJlQ7g1UvqL++Vx8n4CXYWwWO+nR2vcbzCjO9kzJiKLuZGPG7YiTQGPkW/jPPOJ70nYS/VP2Pg7+tqFLB3/iutiv89UJIoVO/r0Bpr8uPoyT090VErzfkg74wuAiNMj2O9ipS+vcMlyVS0ZlFqkjd3lTqLCUaMyd6hlt85IOKTDcVZ3UsmhGx6euBExDJzRMTRwnfwJJb5qRpIbiwCFSsSrDl3WLD5Yi2OdtS3gFzts+dO+YIo75hiBeFSUMBSxBmOEsIuuIpQ0A4yrvKapW5huXaYds7tYKBBTJYv0IrBpn4+4Q2tYkZ4q1bOnmIETTmhgDjslD0C9HVKOpYm5/TRF2zHhIYnisdQ9RtX7EcEHaiWX7bmNdWyk9E3bv/AQ== \ No newline at end of file diff --git a/docs/resources/images/javet_engine_pool.png b/docs/resources/images/javet_engine_pool.png new file mode 100644 index 0000000000000000000000000000000000000000..51a51630d11e79f3c193c42a212c17d35c5d9724 GIT binary patch literal 72147 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 wgU-yb+)KY-wWiCZ2bs&k;F)f!t&k1a0bU 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 literal 0 HcmV?d00001 diff --git a/docs/todo_list.rst b/docs/todo_list.rst index 383cbed75..5a57f3824 100644 --- a/docs/todo_list.rst +++ b/docs/todo_list.rst @@ -2,8 +2,8 @@ TODO List ========= -* To support more types that are not supported by ECMAScript, e.g. BigDecimal, etc. -* To revive NodeJS. +* To support more types that are not supported by ECMAScript, e.g. BigDecimal, byte[], etc. +* To build NodeJS in. * To implement runtime debugging capability. [`Home <../README.rst>`_] diff --git a/docs/tutorial/hello_javet.rst b/docs/tutorial/hello_javet.rst index 2141b833f..516700a38 100644 --- a/docs/tutorial/hello_javet.rst +++ b/docs/tutorial/hello_javet.rst @@ -13,24 +13,22 @@ Maven com.caoccao.javet javet - 0.7.1 + 0.7.2 -Gradle Kotlin -------------- +Gradle Kotlin DSL +----------------- .. code-block:: kotlin - dependencies { - implementation("com.caoccao.javet:javet:0.7.1") - } + implementation("com.caoccao.javet:javet:0.7.2") -Gradle Groovy -------------- +Gradle Groovy DSL +----------------- .. code-block:: groovy - compile group: 'com.caoccao.javet', name: 'javet', version: '0.7.1' + implementation 'com.caoccao.javet:javet:0.7.2' Print **Hello Javet** ===================== @@ -51,9 +49,8 @@ Print **1 + 1** .. code-block:: java // Step 1: Create a V8 runtime from V8 host in try resource. - try (V8Runtime v8Runtime = V8Host.getInstance().createV8Runtime()) { - // Step 2: Request a lock. - v8Runtime.lock(); + // Step 2: Request a lock. + try (V8Runtime v8Runtime = V8Host.getInstance().createV8Runtime().lock()) { // Step 3: Execute a string as JavaScript code and print the result to console. System.out.println("1 + 1 = " + v8Runtime.getExecutor("1 + 1").executeInteger()); // 2 // Step 4: Resource including the lock is recycled automatically at the end of the try resource block. diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index eb8575b86..39de783c0 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -6,6 +6,9 @@ Tutorial * `Know the Lock `_ * `Memory Management `_ * `Manipulate V8 Function `_ +* `Spring Integration `_ +* `Logging `_ +* `Termination `_ * `Polyfill `_ Complete tutorial is available at `here <../../src/test/java/com/caoccao/javet/tutorial>`_. diff --git a/docs/tutorial/logging.rst b/docs/tutorial/logging.rst new file mode 100644 index 000000000..a4d77f373 --- /dev/null +++ b/docs/tutorial/logging.rst @@ -0,0 +1,79 @@ +======= +Logging +======= + +As Javet is a fundamental SDK, it doesn't rely on any other libraries except JDK so that Javet users don't get dependency hell. That also means Javet has to use the JDK logging API, but Javet allows injecting 3rd party logging API. + +Step 1: Implement IJavetLogger +============================== + +``IJavetLogger`` is the the only logging interface accepted by Javet. You may implement ``IJavetLogger`` with ``slf4j`` as following. + +.. code-block:: java + + import com.caoccao.javet.interfaces.IJavetLogger; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class MyJavetLogger implements IJavetLogger { + protected Logger logger; + + public MyJavetLogger(String name) { + logger = LoggerFactory.getLogger(name); + } + + @Override + public void debug(String message) { + if (logger.isDebugEnabled()) { + logger.debug(message); + } + } + + @Override + public void error(String message) { + if (logger.isDebugEnabled()) { + logger.error(message); + } + } + + @Override + public void error(String message, Throwable throwable) { + if (logger.isDebugEnabled()) { + logger.error(message, throwable); + } + } + + @Override + public void info(String message) { + if (logger.isInfoEnabled()) { + logger.info(message); + } + } + + @Override + public void warn(String message) { + if (logger.isWarnEnabled()) { + logger.warn(message); + } + } + } + +Step 2: Inject the Logger +========================= + +Injecting the logger is quite simple. + +* Create an instance of the logger. +* Set the logger to a config. +* Set the config to a pool. + +.. code-block:: java + + MyJavetLogger javetLogger = new MyJavetLogger("TestLogger"); + JavetEngineConfig javetEngineConfig = new JavetEngineConfig(); + javetEngineConfig.setJavetLogger(javetLogger); + JavetEnginePool javetEnginePool = new JavetEnginePool(javetEngineConfig); + +Now, Javet is integrated into your logging system. + +[`Home <../../README.rst>`_] [`Tutorial `_] diff --git a/docs/tutorial/manipulate_v8_function.rst b/docs/tutorial/manipulate_v8_function.rst index 9f9f48e6c..7defa8416 100644 --- a/docs/tutorial/manipulate_v8_function.rst +++ b/docs/tutorial/manipulate_v8_function.rst @@ -36,10 +36,14 @@ Option 2: The Recommended Way // Once this function is set to weak, its lifecycle is automatically managed by Javet + V8. // There is no need to call close() any more. + // Alternatively, setFunction() makes that easy with only one line. + v8ValueObject.setFunction("test", javetCallbackContext); + // An instance of V8ValueFunction is created and set to weak internally. + Automatic Type Conversion ========================= -Javet is capable of automatically converting its internal ``V8Value`` to other types and that capability can be manipulated by ``JavetConverterUtils`` which also supports custom type conversion. So, the following 4 functions are all the same and valid. +Javet is capable of automatically converting its internal ``V8Value`` to primitive types by inspecting the function signature. So, the following 4 functions are all the same and valid. .. code-block:: java @@ -68,4 +72,6 @@ Javet is capable of automatically converting its internal ``V8Value`` to other t Note: Primitive types must be in their object form in the method signature. E.g. ``boolean`` must be set to ``Boolean``, ``int`` must be set to ``Integer``, etc. Why? Because the converted value could be ``null`` which would cause JDK to complain with an exception. +Please review `test cases <../../src/test/java/com/caoccao/javet/values/reference/TestV8ValueFunction.java>`_ for more detail. + [`Home <../../README.rst>`_] [`Tutorial `_] diff --git a/docs/tutorial/memory_management.rst b/docs/tutorial/memory_management.rst index 554b79a93..725474a79 100644 --- a/docs/tutorial/memory_management.rst +++ b/docs/tutorial/memory_management.rst @@ -24,8 +24,8 @@ V8 generally categorizes objects in memory to 3 types. 2. ``v8::Persistent`` - Its lifecycle is managed by V8 GC. 3. ``v8::External`` - V8 GC treats it as root object so that it lives as long as the V8 isolate lives. -Solution: Weak Reference -======================== +Solution 1: Weak Reference +========================== Javet directly borrows the way V8 manages objects in JVM. The rule is simple in the following 2 patterns. @@ -59,4 +59,41 @@ Automatically Manage V8 Objects Note: V8 does not recycle objects that are referenced by other objects. Please make sure the object chain is broken so that GC can work as expected. ``com.caoccao.javet.interception.logging.JavetStandardConsoleInterceptor`` is a good sample showing how to deal with that. +Solution 2: ArrayBuffer +======================= + +The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. + +It is an array of bytes, often referred to in other languages as a "byte array".You cannot directly manipulate the contents of an ArrayBuffer; instead, you create one of the typed array objects or a DataView object which represents the buffer in a specific format, and use that to read and write the contents of the buffer. + +Javet offers complete support to all the typed arrays as well as ``DataView`` as following. There is a ``java.nio.ByteBuffer`` inside every typed array and ``DataView``. That ``ByteBuffer`` directly links to the corresponding backing store of V8 typed array. In other words, Javet and V8 can both access the same address to achieve zero memory copy. Please consider using typed array in performance sensitive scenarios. + ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Type | Value Range | Size in bytes | Description | Web IDL type | Equivalent C type | ++===================+=============================+===============+====================================================================================+=====================+===============================+ +| Int8Array | -128 to 127 | 1 | 8-bit two's complement signed integer | byte | int8_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Uint8Array | 0 to 255 | 1 | 8-bit unsigned integer | octet | uint8_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Uint8ClampedArray | 0 to 255 | 1 | 8-bit unsigned integer (clamped) | octet | uint8_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Int16Array | -32768 to 32767 | 2 | 16-bit two's complement signed integer | short | int16_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Uint16Array | 0 to 65535 | 2 | 16-bit unsigned integer | unsigned short | uint16_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Int32Array | -2147483648 to 2147483647 | 4 | 32-bit two's complement signed integer | long | int32_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Uint32Array | 0 to 4294967295 | 4 | 32-bit unsigned integer | unsigned long | uint32_t | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Float32Array | 1.2×10-38 to 3.4×1038 | 4 | 32-bit IEEE floating point number (7 significant digits e.g., 1.234567) | unrestricted float | float | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| Float64Array | 5.0×10-324 to 1.8×10308 | 8 | 64-bit IEEE floating point number (16 significant digits e.g., 1.23456789012345) | unrestricted double | double | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| BigInt64Array | -263 to 263-1 | 8 | 64-bit two's complement signed integer | bigint | int64_t (signed long long) | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ +| BigUint64Array | 0 to 264-1 | 8 | 64-bit unsigned integer | bigint | uint64_t (unsigned long long) | ++-------------------+-----------------------------+---------------+------------------------------------------------------------------------------------+---------------------+-------------------------------+ + +Please refer to `TestV8ValueTypedArray <../../src/test/java/com/caoccao/javet/values/reference/TestV8ValueTypedArray.java>`_ and `TestV8ValueDataView <../../src/test/java/com/caoccao/javet/values/reference/TestV8ValueDataView.java>`_ for sample code snippets. + [`Home <../../README.rst>`_] [`Tutorial `_] diff --git a/docs/tutorial/polyfill.rst b/docs/tutorial/polyfill.rst index 8ee325473..a5fb807f2 100644 --- a/docs/tutorial/polyfill.rst +++ b/docs/tutorial/polyfill.rst @@ -14,21 +14,36 @@ decimal.js JavetOSUtils.WORKING_DIRECTORY, "scripts/node/node_modules/decimal.js/decimal.js"); if (decimalJSFile.exists() && decimalJSFile.canRead()) { - logger.logInfo("Loading {0}.", decimalJSFile.getAbsolutePath()); - v8Runtime = V8Host.getInstance().createV8Runtime(); - v8Runtime.lock(); + getLogger().logInfo("Loading {0}.", decimalJSFile.getAbsolutePath()); + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); v8Runtime.getExecutor(decimalJSFile).executeVoid(); } else { - logger.logError("{0} is not found.", decimalJSFile.getAbsolutePath()); - logger.logError("Please make sure NodeJS is installed, then visit script/node directory and run npm install."); + getLogger().logError("{0} is not found.", decimalJSFile.getAbsolutePath()); + getLogger().logError("Please make sure NodeJS is installed, then visit script/node directory and run npm install."); } } public void test() throws JavetException { - logger.logInfo("1.23 + 2.34 = {0}", v8Runtime.getExecutor( + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); + getLogger().logInfo("1.23 + 2.34 = {0}", v8Runtime.getExecutor( "const a = new Decimal(1.23);" + "const b = new Decimal(2.34);" + "a.add(b).toString();").executeString()); + try (V8ValueFunction v8ValueFunctionDecimal = v8Runtime.getGlobalObject().get("Decimal")) { + try (V8ValueObject v8ValueObjectDecimal = v8ValueFunctionDecimal.call( + null, new V8ValueString("123.45"))) { + getLogger().logInfo(v8ValueObjectDecimal.toString()); + if (v8ValueObjectDecimal.hasOwnProperty("constructor")) { + try (V8ValueFunction v8ValueFunction = v8ValueObjectDecimal.get("constructor")) { + String name = v8ValueFunction.getString("name"); + if ("Decimal".equals(name)) { + BigDecimal bigDecimal = new BigDecimal(v8ValueObjectDecimal.toString()); + getLogger().logInfo("BigDecimal: {0}", bigDecimal.toString()); + } + } + } + } + } } Please refer to `source code <../../src/test/java/com/caoccao/javet/tutorial/DecimalJavet.java>`_ for more detail. diff --git a/docs/tutorial/spring_integration.rst b/docs/tutorial/spring_integration.rst new file mode 100644 index 000000000..13ccd3ae5 --- /dev/null +++ b/docs/tutorial/spring_integration.rst @@ -0,0 +1,70 @@ +================== +Spring Integration +================== + +As Javet is a fundamental SDK, it doesn't rely on Spring Framework so that Javet users don't get dependency hell. But, Javet can be integrated with Spring easily. + +Configuration +============= + +* Create a Spring configuration. +* Declare ``IJavetEnginePool`` as ``@Bean``. +* Set the pool implement in ``@PostConstruct``. + +.. code-block:: java + + @Configuration + @PropertySource("classpath:javet-engine.properties") + @ConfigurationProperties(prefix = "javet.engine") + public class MyJavetEngineConfig { + @Value("32") + private int poolMaxSize; + @Value("8") + private int poolMinSize; + @Value("60") + private int poolIdleTimeoutSeconds; + @Value("1000") + private int poolDaemonCheckIntervalMillis; + @Value("3600") + private int resetEngineTimeoutSeconds; + private IJavetLogger javetLogger; + private IJavetEnginePool javetEnginePool; + + @PostConstruct + public void postConstruct() { + initializeJavet(); + } + + @PreDestroy + public void preDestroy() throws JavetException { + // There is no need to close Javet engine pool explicitly because spring does so. + } + + @Bean + public IJavetEnginePool getJavetEnginePool() { + return javetEnginePool; + } + + private void initializeJavet() { + javetLogger = new MyJavetLogger("SampleLogger"); + JavetEngineConfig javetEngineConfig = new JavetEngineConfig(); + javetEngineConfig.setPoolDaemonCheckIntervalMillis(getPoolDaemonCheckIntervalMillis()); + javetEngineConfig.setPoolIdleTimeoutSeconds(getPoolIdleTimeoutSeconds()); + javetEngineConfig.setPoolMinSize(getPoolMinSize()); + javetEngineConfig.setPoolMaxSize(getPoolMaxSize()); + javetEngineConfig.setResetEngineTimeoutSeconds(getResetEngineTimeoutSeconds()); + javetEngineConfig.setJavetLogger(javetLogger); + javetEnginePool = new MyJavetEnginePool(javetEngineConfig); + } + +Injection +========= + +You may easily inject your engine pool in the Spring way. + +.. code-block:: java + + @Resource + protected IJavetEnginePool javetEnginePool; + +[`Home <../../README.rst>`_] [`Tutorial `_] diff --git a/docs/tutorial/termination.rst b/docs/tutorial/termination.rst new file mode 100644 index 000000000..13281cffd --- /dev/null +++ b/docs/tutorial/termination.rst @@ -0,0 +1,71 @@ +=========== +Termination +=========== + +Terminating scripts that run out of control is quite important in terms of protecting the applications from being attacked by malicious scripts. In Javet, there are 2 typical ways of terminating scripts. + +Automatic Termination with Pool and Engine +========================================== + +``IJavetEngineGuard`` is the built-in support for terminating a script which runs out of control. + +.. code-block:: java + + // Get an engine from the pool as usual. + try (IJavetEngine iJavetEngine = iJavetEnginePool.getEngine()) { + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); + // Get a guard from the engine and apply try-with-resource pattern. + try (IJavetEngineGuard iJavetEngineGuard = iJavetEngine.getGuard(10000)) { + v8Runtime.getExecutor("while (true) {}").executeVoid(); + // That infinite loop will be terminated in 10 seconds by the guard. + } catch (JavetTerminatedException e) { + // JavetTerminatedException will be thrown to mark that. + assertFalse(e.isContinuable()); + } + assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger(), + "The V8 runtime is not dead and still be able to execute code afterwards."); + } + +Does ``IJavetEngineGuard`` hang normal scripts till timeout is hit? No, it doesn't cause any overhead. If the script completes, ``IJavetEngineGuard.close()`` will be called via try-with-resource pattern and cancel the daemon thread immediately. + +Please refer to `source code <../../src/test/java/com/caoccao/javet/interop/engine/TestJavetEngineGuard.java>`_ for more detail. + +Manual Termination +================== + +Manual termination gives applications complete control. In return, the coding effort is considerable. + +.. code-block:: java + + V8Host v8Host = V8Host.getInstance(); + try (V8Runtime v8Runtime = v8Host.createV8Runtime()) { + // Create a daemon thread monitoring the V8 runtime status. + Thread daemonThread = new Thread(() -> { + // V8 runtime isInUse() does not require lock. + while (!v8Runtime.isInUse()) { + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + // V8 runtime terminateExecution() does not require lock. + v8Runtime.terminateExecution(); + }); + daemonThread.start(); + v8Runtime.lock(); + try { + v8Runtime.getExecutor( + "var count = 0; while (true) { ++count; }") + .executeVoid(); + fail("Failed to throw exception when execution is terminated."); + } catch (JavetTerminatedException e) { + assertFalse(e.isContinuable()); + } + final int count = v8Runtime.getGlobalObject().getInteger("count"); + assertTrue(count > 0, "Count should be greater than 0."); + assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger(), + "V8 runtime should still be able to execute script after being terminated."); + } + +[`Home <../../README.rst>`_] [`Tutorial `_] diff --git a/pom.xml b/pom.xml index 028e5fa7d..ad8da3b90 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.caoccao.javet javet - 0.7.1 + 0.7.2 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.7.1 + javet-0.7.2 diff --git a/scripts/python/change_javet_version.py b/scripts/python/change_javet_version.py index 5eda91f5c..f82d40ac7 100644 --- a/scripts/python/change_javet_version.py +++ b/scripts/python/change_javet_version.py @@ -32,9 +32,9 @@ def __init__(self, version) -> None: def update(self): self._update( 'README.rst', '\n', - re.compile(r'\*(?P\d+\.\d+\.\d+)\*'), 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'version: \'(?P\d+\.\d+\.\d+)\'')) self._update( 'build.gradle.kts', '\n', @@ -43,13 +43,14 @@ def update(self): 'docs/tutorial/hello_javet.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'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', '\n', + 'cpp/build.cmd', '\r\n', re.compile(r'JAVET_VERSION=(?P\d+\.\d+\.\d+)$')) self._update( 'cpp/build.sh', '\n', @@ -58,7 +59,7 @@ def update(self): 'src/main/java/com/caoccao/javet/interop/JavetLibLoader.java', '\n', re.compile(r'LIB_VERSION = "(?P\d+\.\d+\.\d+)";$')) self._update( - 'cpp/jni/javet.rc', '\n', + 'cpp/jni/javet.rc', '\r\n', re.compile(r'"(?P\d+\.\d+\.\d+)'), re.compile(r'v\.(?P\d+\.\d+\.\d+)'), re.compile(r'(?P\d+,\d+,\d+)')) @@ -67,7 +68,8 @@ def _update(self, relative_file_path: str, line_separator: str, *patterns: list) file_path = (self._root_path / relative_file_path).resolve().absolute() logging.info('Updating %s.', str(file_path)) lines, line_number = [], 1 - for line in file_path.read_text('utf-8').split(line_separator): + original_buffer = file_path.read_bytes() + for line in original_buffer.decode('utf-8').split(line_separator): for pattern in patterns: match_object = pattern.search(line) if match_object is not None: @@ -86,10 +88,15 @@ def _update(self, relative_file_path: str, line_separator: str, *patterns: list) break lines.append(line) line_number += 1 - file_path.write_text(line_separator.join(lines), 'utf-8') + new_buffer = line_separator.join(lines).encode('utf-8') + if original_buffer == new_buffer: + logging.warn(' Skipped.') + else: + file_path.write_bytes(new_buffer) + logging.info(' Updated.') def main(): - change_javet_version = ChangeJavetVersion('0.7.1') + change_javet_version = ChangeJavetVersion('0.7.2') change_javet_version.update() return 0 diff --git a/src/main/java/com/caoccao/javet/entities/JavetEntityMap.java b/src/main/java/com/caoccao/javet/entities/JavetEntityMap.java new file mode 100644 index 000000000..30c6c47a6 --- /dev/null +++ b/src/main/java/com/caoccao/javet/entities/JavetEntityMap.java @@ -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. + */ + +package com.caoccao.javet.entities; + +import java.util.HashMap; +import java.util.Map; + +public class JavetEntityMap extends HashMap { + public JavetEntityMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public JavetEntityMap(int initialCapacity) { + super(initialCapacity); + } + + public JavetEntityMap() { + } + + public JavetEntityMap(Map m) { + super(m); + } + + @Override + public Object clone() { + return new JavetEntityMap(this); + } +} diff --git a/src/main/java/com/caoccao/javet/exceptions/JavetTerminatedException.java b/src/main/java/com/caoccao/javet/exceptions/JavetTerminatedException.java new file mode 100644 index 000000000..d42fe569a --- /dev/null +++ b/src/main/java/com/caoccao/javet/exceptions/JavetTerminatedException.java @@ -0,0 +1,33 @@ +/* + * 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.exceptions; + +import java.text.MessageFormat; + +public class JavetTerminatedException extends JavetException { + protected boolean continuable; + + public JavetTerminatedException(boolean continuable) { + super(MessageFormat.format("V8 execution is terminated, continuable: {0}", continuable)); + this.continuable = continuable; + } + + public boolean isContinuable() { + return continuable; + } +} diff --git a/src/main/java/com/caoccao/javet/exceptions/JavetV8RuntimeLockConflictException.java b/src/main/java/com/caoccao/javet/exceptions/JavetV8RuntimeLockConflictException.java index ac5ba768b..0ea288f2c 100644 --- a/src/main/java/com/caoccao/javet/exceptions/JavetV8RuntimeLockConflictException.java +++ b/src/main/java/com/caoccao/javet/exceptions/JavetV8RuntimeLockConflictException.java @@ -20,16 +20,16 @@ import java.text.MessageFormat; public class JavetV8RuntimeLockConflictException extends JavetException { - public JavetV8RuntimeLockConflictException(String message) { + protected JavetV8RuntimeLockConflictException(String message) { super(message); } - public JavetV8RuntimeLockConflictException() { - this("V8 runtime lock conflict is detected"); + public static JavetV8RuntimeLockConflictException lockNotRequired() { + return new JavetV8RuntimeLockConflictException("V8 runtime lock is not required"); } - public JavetV8RuntimeLockConflictException(long lockedThreadId, long currentThreadId) { - this(MessageFormat.format( + public static JavetV8RuntimeLockConflictException threadIdMismatch(long lockedThreadId, long currentThreadId) { + return new JavetV8RuntimeLockConflictException(MessageFormat.format( "V8 runtime lock conflict is detected with locked thread ID {0} and current thread ID {1}", Long.toString(lockedThreadId), Long.toString(currentThreadId))); } diff --git a/src/main/java/com/caoccao/javet/interop/IV8Creatable.java b/src/main/java/com/caoccao/javet/interop/IV8Creatable.java index 1d99918e3..cab60d21c 100644 --- a/src/main/java/com/caoccao/javet/interop/IV8Creatable.java +++ b/src/main/java/com/caoccao/javet/interop/IV8Creatable.java @@ -19,16 +19,28 @@ import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.utils.JavetCallbackContext; +import com.caoccao.javet.values.primitive.V8ValueNull; +import com.caoccao.javet.values.primitive.V8ValueUndefined; import com.caoccao.javet.values.reference.*; public interface IV8Creatable { V8ValueArray createV8ValueArray() throws JavetException; + V8ValueArrayBuffer createV8ValueArrayBuffer(int length) throws JavetException; + + V8ValueDataView createV8ValueDataView(V8ValueArrayBuffer v8ValueArrayBuffer) throws JavetException; + V8ValueFunction createV8ValueFunction(JavetCallbackContext javetCallbackContext) throws JavetException; V8ValueMap createV8ValueMap() throws JavetException; + V8ValueNull createV8ValueNull(); + V8ValueObject createV8ValueObject() throws JavetException; V8ValueSet createV8ValueSet() throws JavetException; + + V8ValueTypedArray createV8ValueTypedArray(int type, int length) throws JavetException; + + V8ValueUndefined createV8ValueUndefined(); } diff --git a/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java b/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java index 77d810009..e892b17d7 100644 --- a/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java +++ b/src/main/java/com/caoccao/javet/interop/JavetLibLoader.java @@ -1,122 +1,123 @@ -/* - * 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.interop; - -import com.caoccao.javet.exceptions.JavetIOException; -import com.caoccao.javet.exceptions.JavetOSNotSupportedException; -import com.caoccao.javet.utils.JavetOSUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.text.MessageFormat; - -final class JavetLibLoader { - private static final String CHMOD = "chmod"; - private static final String XRR = "755"; - static final String LIB_VERSION = "0.7.1"; - private static final String LIB_FILE_NAME_FORMAT = "libjavet-{0}-x86_64.v.{1}.{2}"; - private static final String RESOURCE_NAME_FORMAT = "/{0}"; - private static final String LIB_FILE_EXTENSION_LINUX = "so"; - private static final String LIB_FILE_EXTENSION_WINDOWS = "dll"; - private static final String OS_LINUX = "linux"; - private static final String OS_WINDOWS = "windows"; - private static int BUFFER_LENGTH = 4096; - - private static Object lockObject = new Object(); - private static boolean javetLibLoaded = false; - - private JavetLibLoader() { - } - - static boolean load() throws JavetOSNotSupportedException, JavetIOException { - if (!javetLibLoaded) { - synchronized (lockObject) { - if (!javetLibLoaded) { - internalLoad(); - javetLibLoaded = true; - } - } - } - return javetLibLoaded; - } - - private static boolean deployLibFile(File libFile) { - boolean isDeployed = false; - boolean isLibFileLocked = false; - if (libFile.exists()) { - try { - libFile.delete(); - } catch (Exception e) { - isLibFileLocked = true; - } - } - if (!isLibFileLocked) { - byte[] buffer = new byte[BUFFER_LENGTH]; - String resourceName = MessageFormat.format(RESOURCE_NAME_FORMAT, libFile.getName()); - try (InputStream inputStream = JavetLibLoader.class.getResourceAsStream(resourceName); - FileOutputStream outputStream = new FileOutputStream(libFile.getAbsolutePath())) { - while (true) { - int length = inputStream.read(buffer); - if (length == -1) { - break; - } - outputStream.write(buffer, 0, length); - } - isDeployed = true; - } catch (Exception e) { - // Lib file is locked. - } - if (isDeployed && JavetOSUtils.IS_LINUX) { - try { - Runtime.getRuntime().exec(new String[]{CHMOD, XRR, libFile.getAbsolutePath()}).waitFor(); - } catch (Throwable e) { - } - } - } - return isDeployed; - } - - private static File getLibFile(String rootDirectory) throws JavetOSNotSupportedException { - if (JavetOSUtils.IS_WINDOWS) { - return new File( - rootDirectory, - MessageFormat.format( - LIB_FILE_NAME_FORMAT, - OS_WINDOWS, LIB_VERSION, LIB_FILE_EXTENSION_WINDOWS)); - } else if (JavetOSUtils.IS_LINUX) { - return new File( - rootDirectory, - MessageFormat.format( - LIB_FILE_NAME_FORMAT, - OS_LINUX, LIB_VERSION, LIB_FILE_EXTENSION_LINUX)); - } else { - throw new JavetOSNotSupportedException(JavetOSUtils.OS_NAME); - } - } - - private static void internalLoad() throws JavetOSNotSupportedException, JavetIOException { - File tempDirectoryLibFile = getLibFile(JavetOSUtils.TEMP_DIRECTORY); - try { - deployLibFile(tempDirectoryLibFile); - System.load(tempDirectoryLibFile.getAbsolutePath()); - } catch (Throwable t) { - throw JavetIOException.failedToReadPath(tempDirectoryLibFile.toPath(), t); - } - } -} +/* + * 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.interop; + +import com.caoccao.javet.exceptions.JavetIOException; +import com.caoccao.javet.exceptions.JavetOSNotSupportedException; +import com.caoccao.javet.utils.JavetOSUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.text.MessageFormat; + +final class JavetLibLoader { + private static final String CHMOD = "chmod"; + private static final String XRR = "755"; + static final String LIB_VERSION = "0.7.2"; + static final String V8_VERSION = "8.9.255"; + private static final String LIB_FILE_NAME_FORMAT = "libjavet-{0}-x86_64.v.{1}.{2}"; + private static final String RESOURCE_NAME_FORMAT = "/{0}"; + private static final String LIB_FILE_EXTENSION_LINUX = "so"; + private static final String LIB_FILE_EXTENSION_WINDOWS = "dll"; + private static final String OS_LINUX = "linux"; + private static final String OS_WINDOWS = "windows"; + private static int BUFFER_LENGTH = 4096; + + private static Object lockObject = new Object(); + private static boolean javetLibLoaded = false; + + private JavetLibLoader() { + } + + static boolean load() throws JavetOSNotSupportedException, JavetIOException { + if (!javetLibLoaded) { + synchronized (lockObject) { + if (!javetLibLoaded) { + internalLoad(); + javetLibLoaded = true; + } + } + } + return javetLibLoaded; + } + + private static boolean deployLibFile(File libFile) { + boolean isDeployed = false; + boolean isLibFileLocked = false; + if (libFile.exists()) { + try { + libFile.delete(); + } catch (Exception e) { + isLibFileLocked = true; + } + } + if (!isLibFileLocked) { + byte[] buffer = new byte[BUFFER_LENGTH]; + String resourceName = MessageFormat.format(RESOURCE_NAME_FORMAT, libFile.getName()); + try (InputStream inputStream = JavetLibLoader.class.getResourceAsStream(resourceName); + FileOutputStream outputStream = new FileOutputStream(libFile.getAbsolutePath())) { + while (true) { + int length = inputStream.read(buffer); + if (length == -1) { + break; + } + outputStream.write(buffer, 0, length); + } + isDeployed = true; + } catch (Exception e) { + // Lib file is locked. + } + if (isDeployed && JavetOSUtils.IS_LINUX) { + try { + Runtime.getRuntime().exec(new String[]{CHMOD, XRR, libFile.getAbsolutePath()}).waitFor(); + } catch (Throwable e) { + } + } + } + return isDeployed; + } + + private static File getLibFile(String rootDirectory) throws JavetOSNotSupportedException { + if (JavetOSUtils.IS_WINDOWS) { + return new File( + rootDirectory, + MessageFormat.format( + LIB_FILE_NAME_FORMAT, + OS_WINDOWS, LIB_VERSION, LIB_FILE_EXTENSION_WINDOWS)); + } else if (JavetOSUtils.IS_LINUX) { + return new File( + rootDirectory, + MessageFormat.format( + LIB_FILE_NAME_FORMAT, + OS_LINUX, LIB_VERSION, LIB_FILE_EXTENSION_LINUX)); + } else { + throw new JavetOSNotSupportedException(JavetOSUtils.OS_NAME); + } + } + + private static void internalLoad() throws JavetOSNotSupportedException, JavetIOException { + File tempDirectoryLibFile = getLibFile(JavetOSUtils.TEMP_DIRECTORY); + try { + deployLibFile(tempDirectoryLibFile); + System.load(tempDirectoryLibFile.getAbsolutePath()); + } catch (Throwable t) { + throw JavetIOException.failedToReadPath(tempDirectoryLibFile.toPath(), t); + } + } +} diff --git a/src/main/java/com/caoccao/javet/interop/V8Host.java b/src/main/java/com/caoccao/javet/interop/V8Host.java index 6ea72f257..6a8ad7b93 100644 --- a/src/main/java/com/caoccao/javet/interop/V8Host.java +++ b/src/main/java/com/caoccao/javet/interop/V8Host.java @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; public final class V8Host implements AutoCloseable { + public static final String GLOBAL_THIS = "globalThis"; private static final long INVALID_HANDLE = 0L; private static final String FLAG_ALLOW_NATIVES_SYNTAX = "--allow-natives-syntax"; private static final String FLAG_EXPOSE_GC = "--expose_gc"; @@ -66,10 +67,6 @@ public static V8Host getInstance() { return instance; } - public V8Runtime createV8Runtime() { - return createV8Runtime(null); - } - public V8Flags getFlags() { return flags; } @@ -78,6 +75,10 @@ public String getJavetVersion() { return JavetLibLoader.LIB_VERSION; } + public V8Runtime createV8Runtime() { + return createV8Runtime(GLOBAL_THIS); + } + public V8Runtime createV8Runtime(String globalName) { return createV8Runtime(false, globalName); } diff --git a/src/main/java/com/caoccao/javet/interop/V8Native.java b/src/main/java/com/caoccao/javet/interop/V8Native.java index ccc179f19..fb55a14d2 100644 --- a/src/main/java/com/caoccao/javet/interop/V8Native.java +++ b/src/main/java/com/caoccao/javet/interop/V8Native.java @@ -37,6 +37,9 @@ native static Object call( long v8RuntimeHandle, long v8ValueHandle, int v8ValueType, Object receiver, boolean returnResult, Object[] values); + native static Object callAsConstructor( + long v8RuntimeHandle, long v8ValueHandle, int v8ValueType, Object[] values); + native static void clearWeak(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); native static Object cloneV8Value( @@ -55,6 +58,8 @@ native static void compileOnly( native static boolean delete(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType, Object key); + native static boolean equals(long v8RuntimeHandle, long v8ValueHandle1, long v8ValueHandle2); + native static Object execute( long v8RuntimeHandle, String script, boolean returnResult, String resourceName, int resourceLineOffset, int resourceColumnOffset, @@ -64,6 +69,8 @@ native static Object execute( native static Object getGlobalObject(long v8RuntimeHandle); + native static int getIdentityHash(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); + native static int getLength(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); native static int getSize(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); @@ -84,6 +91,10 @@ native static Object invoke( long v8RuntimeHandle, long v8ValueHandle, int v8ValueType, String functionName, boolean returnResult, Object[] values); + native static boolean isDead(long v8RuntimeHandle); + + native static boolean isInUse(long v8RuntimeHandle); + native static boolean isWeak(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); native static void lockV8Runtime(long v8RuntimeHandle); @@ -114,6 +125,12 @@ native static Object invoke( native static void setWeak(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType, Object objectReference); + native static boolean sameValue(long v8RuntimeHandle, long v8ValueHandle1, long v8ValueHandle2); + + native static boolean strictEquals(long v8RuntimeHandle, long v8ValueHandle1, long v8ValueHandle2); + + native static void terminateExecution(long v8RuntimeHandle); + native static String toProtoString(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); native static String toString(long v8RuntimeHandle, long v8ValueHandle, int v8ValueType); diff --git a/src/main/java/com/caoccao/javet/interop/V8Runtime.java b/src/main/java/com/caoccao/javet/interop/V8Runtime.java index fce3ad934..72427ff46 100644 --- a/src/main/java/com/caoccao/javet/interop/V8Runtime.java +++ b/src/main/java/com/caoccao/javet/interop/V8Runtime.java @@ -30,6 +30,9 @@ import com.caoccao.javet.utils.JavetDefaultLogger; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.V8ValueReferenceType; +import com.caoccao.javet.values.primitive.V8ValueInteger; +import com.caoccao.javet.values.primitive.V8ValueNull; +import com.caoccao.javet.values.primitive.V8ValueUndefined; import com.caoccao.javet.values.reference.*; import java.nio.file.Path; @@ -42,6 +45,7 @@ public final class V8Runtime implements IJavetClosable, IV8Executable, IV8Creatable { private static final long INVALID_THREAD_ID = -1L; private static final long INVALID_HANDLE = 0L; + private static final String PROPERTY_DATA_VIEW = "DataView"; private String globalName; private long handle; @@ -83,13 +87,21 @@ public T call( handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), receiver, returnResult, v8Values)); } + public T callAsConstructor( + IV8ValueObject iV8ValueObject, V8Value... v8Values) throws JavetException { + checkLock(); + decorateV8Values(v8Values); + return decorateV8Value((T) V8Native.callAsConstructor( + handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), v8Values)); + } + public V8Runtime checkLock() throws JavetV8RuntimeLockConflictException, JavetV8RuntimeAlreadyClosedException { if (handle == INVALID_HANDLE) { throw new JavetV8RuntimeAlreadyClosedException(); } final long currentThreadId = Thread.currentThread().getId(); if (threadId != currentThreadId) { - throw new JavetV8RuntimeLockConflictException(threadId, currentThreadId); + throw JavetV8RuntimeLockConflictException.threadIdMismatch(threadId, currentThreadId); } return this; } @@ -140,14 +152,30 @@ public void compileOnly(String scriptString, V8ScriptOrigin v8ScriptOrigin) thro @Override public V8ValueArray createV8ValueArray() throws JavetException { checkLock(); - return decorateV8Value((V8ValueArray) V8Native.createV8Value(handle, V8ValueReferenceType.Array, null)); + return decorateV8Value((V8ValueArray) V8Native.createV8Value( + handle, V8ValueReferenceType.Array, null)); + } + + @Override + public V8ValueArrayBuffer createV8ValueArrayBuffer(int length) throws JavetException { + checkLock(); + return decorateV8Value((V8ValueArrayBuffer) V8Native.createV8Value( + handle, V8ValueReferenceType.ArrayBuffer, new V8ValueInteger(length))); + } + + @Override + public V8ValueDataView createV8ValueDataView(V8ValueArrayBuffer v8ValueArrayBuffer) throws JavetException { + checkLock(); + try (V8ValueFunction v8ValueFunction = getGlobalObject().get(PROPERTY_DATA_VIEW)) { + return v8ValueFunction.callAsConstructor(v8ValueArrayBuffer); + } } @Override public V8ValueFunction createV8ValueFunction(JavetCallbackContext javetCallbackContext) throws JavetException { checkLock(); - V8ValueFunction v8ValueFunction = decorateV8Value( - (V8ValueFunction) V8Native.createV8Value(handle, V8ValueReferenceType.Function, javetCallbackContext)); + V8ValueFunction v8ValueFunction = decorateV8Value((V8ValueFunction) V8Native.createV8Value( + handle, V8ValueReferenceType.Function, javetCallbackContext)); v8ValueFunction.setV8CallbackContext(javetCallbackContext); return v8ValueFunction; } @@ -158,6 +186,16 @@ public V8ValueMap createV8ValueMap() throws JavetException { return decorateV8Value((V8ValueMap) V8Native.createV8Value(handle, V8ValueReferenceType.Map, null)); } + @Override + public V8ValueNull createV8ValueNull() { + V8ValueNull v8ValueNull = new V8ValueNull(); + try { + v8ValueNull.setV8Runtime(this); + } catch (JavetException javetException) { + } + return v8ValueNull; + } + @Override public V8ValueObject createV8ValueObject() throws JavetException { checkLock(); @@ -170,6 +208,23 @@ public V8ValueSet createV8ValueSet() throws JavetException { return decorateV8Value((V8ValueSet) V8Native.createV8Value(handle, V8ValueReferenceType.Set, null)); } + @Override + public V8ValueTypedArray createV8ValueTypedArray(int type, int length) throws JavetException { + try (V8ValueFunction v8ValueFunction = getGlobalObject().get(V8ValueTypedArray.getName(type))) { + return v8ValueFunction.callAsConstructor(new V8ValueInteger(length)); + } + } + + @Override + public V8ValueUndefined createV8ValueUndefined() { + V8ValueUndefined v8ValueUndefined = new V8ValueUndefined(); + try { + v8ValueUndefined.setV8Runtime(this); + } catch (JavetException javetException) { + } + return v8ValueUndefined; + } + public T decorateV8Value(T v8Value) throws JavetException { if (v8Value != null) { if (v8Value.getV8Runtime() == null) { @@ -197,6 +252,13 @@ public boolean delete(IV8ValueObject iV8ValueObject, V8Value key) throws JavetEx return V8Native.delete(handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), key); } + public boolean equals(V8ValueReference v8ValueReference1, V8ValueReference v8ValueReference2) + throws JavetException { + checkLock(); + decorateV8Values(v8ValueReference1, v8ValueReference2); + return V8Native.equals(handle, v8ValueReference1.getHandle(), v8ValueReference2.getHandle()); + } + @Override public T execute( String scriptString, V8ScriptOrigin v8ScriptOrigin, boolean resultRequired) throws JavetException { @@ -232,6 +294,11 @@ public void setGlobalName(String globalName) { this.globalName = globalName; } + public int getIdentityHash(IV8ValueObject iV8ValueObject) throws JavetException { + checkLock(); + return V8Native.getIdentityHash(handle, iV8ValueObject.getHandle(), iV8ValueObject.getType()); + } + public IJavetLogger getLogger() { return logger; } @@ -253,6 +320,11 @@ public int getLength(IV8ValueArray iV8ValueArray) throws JavetException { return V8Native.getLength(handle, iV8ValueArray.getHandle(), iV8ValueArray.getType()); } + public int getLength(IV8ValueTypedArray iV8ValueTypedArray) throws JavetException { + checkLock(); + return V8Native.getLength(handle, iV8ValueTypedArray.getHandle(), iV8ValueTypedArray.getType()); + } + public IV8ValueArray getOwnPropertyNames( IV8ValueObject iV8ValueObject) throws JavetException { checkLock(); @@ -286,16 +358,16 @@ public int getSize(IV8ValueKeyContainer iV8ValueKeyContainer) throws JavetExcept return V8Native.getSize(handle, iV8ValueKeyContainer.getHandle(), iV8ValueKeyContainer.getType()); } - public boolean hasOwnProperty(IV8ValueObject iV8ValueObject, V8Value key) throws JavetException { + public boolean has(IV8ValueObject iV8ValueObject, V8Value value) throws JavetException { checkLock(); - decorateV8Value(key); - return V8Native.hasOwnProperty(handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), key); + decorateV8Value(value); + return V8Native.has(handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), value); } - public boolean has(IV8ValueKeyContainer iV8ValueKeyContainer, V8Value value) throws JavetException { + public boolean hasOwnProperty(IV8ValueObject iV8ValueObject, V8Value key) throws JavetException { checkLock(); - decorateV8Value(value); - return V8Native.has(handle, iV8ValueKeyContainer.getHandle(), iV8ValueKeyContainer.getType(), value); + decorateV8Value(key); + return V8Native.hasOwnProperty(handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), key); } public T invoke( @@ -307,6 +379,14 @@ public T invoke( handle, iV8ValueObject.getHandle(), iV8ValueObject.getType(), functionName, returnResult, v8Values)); } + public boolean isDead() { + return V8Native.isDead(handle); + } + + public boolean isInUse() { + return V8Native.isInUse(handle); + } + public boolean isLocked() { return threadId != INVALID_THREAD_ID; } @@ -387,36 +467,32 @@ public void requestGarbageCollectionForTesting(boolean fullGC) * Reset V8 context. * This is a light-weight and recommended reset. * + * @return the self * @throws JavetException the javet exception */ - public void resetContext() throws JavetException { - removeReferences(); - boolean locked = isLocked(); - if (locked) { - unlock(); + public V8Runtime resetContext() throws JavetException { + if (isLocked()) { + throw JavetV8RuntimeLockConflictException.lockNotRequired(); } + removeReferences(); V8Native.resetV8Context(handle, globalName); - if (locked) { - lock(); - } + return this; } /** * Reset V8 isolate. * This is a heavy reset. Please avoid using it in performance sensitive scenario. * + * @return the self * @throws JavetException the javet exception */ - public void resetIsolate() throws JavetException { - removeReferences(); - boolean locked = isLocked(); - if (locked) { - unlock(); + public V8Runtime resetIsolate() throws JavetException { + if (isLocked()) { + throw JavetV8RuntimeLockConflictException.lockNotRequired(); } + removeReferences(); V8Native.resetV8Isolate(handle, globalName); - if (locked) { - lock(); - } + return this; } public boolean set(IV8ValueObject iV8ValueObject, V8Value key, V8Value value) throws JavetException { @@ -436,6 +512,33 @@ public void setWeak(IV8ValueReference iV8ValueReference) throws JavetException { V8Native.setWeak(handle, iV8ValueReference.getHandle(), iV8ValueReference.getType(), iV8ValueReference); } + public boolean sameValue(V8ValueReference v8ValueReference1, V8ValueReference v8ValueReference2) + throws JavetException { + checkLock(); + decorateV8Values(v8ValueReference1, v8ValueReference2); + return V8Native.sameValue(handle, v8ValueReference1.getHandle(), v8ValueReference2.getHandle()); + } + + public boolean strictEquals(V8ValueReference v8ValueReference1, V8ValueReference v8ValueReference2) + throws JavetException { + checkLock(); + decorateV8Values(v8ValueReference1, v8ValueReference2); + return V8Native.strictEquals(handle, v8ValueReference1.getHandle(), v8ValueReference2.getHandle()); + } + + /** + * Terminate execution. + *

+ * Forcefully terminate the current thread of JavaScript execution + * in the given isolate. + *

+ * This method can be used by any thread even if that thread has not + * acquired the V8 lock with a Locker object. + */ + public void terminateExecution() { + V8Native.terminateExecution(handle); + } + public String toProtoString(IV8ValueReference iV8ValueReference) throws JavetV8RuntimeLockConflictException, JavetV8RuntimeAlreadyClosedException { checkLock(); diff --git a/src/main/java/com/caoccao/javet/interop/engine/IJavetEngine.java b/src/main/java/com/caoccao/javet/interop/engine/IJavetEngine.java index 4acceab5f..8635a392b 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/IJavetEngine.java +++ b/src/main/java/com/caoccao/javet/interop/engine/IJavetEngine.java @@ -22,9 +22,17 @@ import com.caoccao.javet.interop.V8Runtime; public interface IJavetEngine extends IJavetClosable { + JavetEngineConfig getConfig(); + V8Runtime getV8Runtime() throws JavetException; + IJavetEngineGuard getGuard(); + + IJavetEngineGuard getGuard(long timeoutMillis); + boolean isActive(); void resetContext() throws JavetException; + + void resetContext(boolean skipLock) throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/interop/engine/IJavetEngineGuard.java b/src/main/java/com/caoccao/javet/interop/engine/IJavetEngineGuard.java new file mode 100644 index 000000000..fdb75fe91 --- /dev/null +++ b/src/main/java/com/caoccao/javet/interop/engine/IJavetEngineGuard.java @@ -0,0 +1,53 @@ +/* + * 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.engine; + +import com.caoccao.javet.interfaces.IJavetClosable; + +/** + * The interface Javet engine guard is the one guarding the script execution with a timeout. + *

+ * Usage: + * + * + * try (IJavetEngineGuard iJavetEngineGuard = iJavetEngine.getGuard(5000)) { + * v8Runtime.getExecutor("while (true) {}").executeVoid(); + * // That infinite loop will be terminated in 5 seconds by the guard. + * } + * + */ +public interface IJavetEngineGuard extends IJavetClosable, Runnable { + /** + * Cancel. + */ + void cancel(); + + /** + * Gets timeout millis. + * + * @return the timeout millis + */ + long getTimeoutMillis(); + + /** + * Sets timeout millis. + * + * @param timeoutMillis the timeout millis + */ + void setTimeoutMillis(long timeoutMillis); +} diff --git a/src/main/java/com/caoccao/javet/interop/engine/IJavetEnginePool.java b/src/main/java/com/caoccao/javet/interop/engine/IJavetEnginePool.java index 34cba8859..284b4035e 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/IJavetEnginePool.java +++ b/src/main/java/com/caoccao/javet/interop/engine/IJavetEnginePool.java @@ -28,5 +28,9 @@ public interface IJavetEnginePool extends IJavetClosable { int getIdleEngineCount(); + boolean isActive(); + + boolean isQuitting(); + void releaseEngine(IJavetEngine engine); } 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 7591022eb..def94aca0 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEngine.java @@ -55,6 +55,21 @@ protected void close(boolean forceClose) throws JavetException { } } + @Override + public JavetEngineConfig getConfig() { + return iJavetEnginePool.getConfig(); + } + + @Override + public IJavetEngineGuard getGuard() { + return getGuard(iJavetEnginePool.getConfig().getDefaultEngineGuardTimeoutMillis()); + } + + @Override + public IJavetEngineGuard getGuard(long timeoutMillis) { + return new JavetEngineGuard(this, v8Runtime, timeoutMillis); + } + protected JavetEngineUsage getUsage() { return usage; } @@ -79,13 +94,34 @@ protected ZonedDateTime getUTCNow() { @Override public void resetContext() throws JavetException { + resetContext(false); + } + + @Override + public void resetContext(boolean skipLock) throws JavetException { + if (!skipLock) { + v8Runtime.unlock(); + } v8Runtime.resetContext(); usage.reset(); + if (!skipLock) { + v8Runtime.lock(); + } } protected void resetIsolate() throws JavetException { + resetIsolate(false); + } + + protected void resetIsolate(boolean skipLock) throws JavetException { + if (!skipLock) { + v8Runtime.unlock(); + } v8Runtime.resetIsolate(); usage.reset(); + if (!skipLock) { + v8Runtime.lock(); + } } protected void touchLastActiveZonedDateTime() { diff --git a/src/main/java/com/caoccao/javet/interop/engine/JavetEngineConfig.java b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineConfig.java index 8f4f2d63e..c376a4353 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEngineConfig.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineConfig.java @@ -22,23 +22,31 @@ import com.caoccao.javet.utils.JavetOSUtils; import java.util.Objects; +import java.util.concurrent.ExecutorService; public final class JavetEngineConfig { + public static final int DEFAULT_ENGINE_GUARD_TIMEOUT_MILLIS = 30000; + public static final int DEFAULT_ENGINE_GUARD_CHECK_INTERVAL_MILLIS = 1000; public static final int DEFAULT_MAX_ENGINE_USED_COUNT = 100; public static final int DEFAULT_POOL_MIN_SIZE = 1; public static final int DEFAULT_POOL_IDLE_TIMEOUT_SECONDS = 60; public static final int DEFAULT_POOL_DAEMON_CHECK_INTERVAL_MILLIS = 1000; public static final int DEFAULT_RESET_ENGINE_TIMEOUT_SECONDS = 3600; public static final String DEFAULT_GLOBAL_NAME = "window"; + public static final int DEFAULT_POOL_SHUTDOWN_TIMEOUT_SECONDS = 5; public static IJavetLogger DEFAULT_JAVET_LOGGER = new JavetDefaultLogger(JavetEnginePool.class.getName()); private IJavetLogger javetLogger; private String globalName; + private int defaultEngineGuardTimeoutMillis; + private int engineGuardCheckIntervalMillis; private int maxEngineUsedCount; + private int poolDaemonCheckIntervalMillis; private int poolMaxSize; private int poolMinSize; private int poolIdleTimeoutSeconds; - private int poolDaemonCheckIntervalMillis; + private int poolShutdownTimeoutSeconds; private int resetEngineTimeoutSeconds; + private ExecutorService executorService; public JavetEngineConfig() { reset(); @@ -47,15 +55,42 @@ public JavetEngineConfig() { public void reset() { javetLogger = DEFAULT_JAVET_LOGGER; globalName = DEFAULT_GLOBAL_NAME; + defaultEngineGuardTimeoutMillis = DEFAULT_ENGINE_GUARD_TIMEOUT_MILLIS; + engineGuardCheckIntervalMillis = DEFAULT_ENGINE_GUARD_CHECK_INTERVAL_MILLIS; maxEngineUsedCount = DEFAULT_MAX_ENGINE_USED_COUNT; final int cpuCount = JavetOSUtils.getCPUCount(); poolMinSize = Math.max(DEFAULT_POOL_MIN_SIZE, cpuCount >> 1); poolMaxSize = Math.max(DEFAULT_POOL_MIN_SIZE, cpuCount); poolIdleTimeoutSeconds = DEFAULT_POOL_IDLE_TIMEOUT_SECONDS; + poolShutdownTimeoutSeconds = DEFAULT_POOL_SHUTDOWN_TIMEOUT_SECONDS; poolDaemonCheckIntervalMillis = DEFAULT_POOL_DAEMON_CHECK_INTERVAL_MILLIS; resetEngineTimeoutSeconds = DEFAULT_RESET_ENGINE_TIMEOUT_SECONDS; } + public ExecutorService getExecutorService() { + return executorService; + } + + void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + public int getPoolShutdownTimeoutSeconds() { + return poolShutdownTimeoutSeconds; + } + + public void setPoolShutdownTimeoutSeconds(int poolShutdownTimeoutSeconds) { + this.poolShutdownTimeoutSeconds = poolShutdownTimeoutSeconds; + } + + public int getEngineGuardCheckIntervalMillis() { + return engineGuardCheckIntervalMillis; + } + + public void setEngineGuardCheckIntervalMillis(int engineGuardCheckIntervalMillis) { + this.engineGuardCheckIntervalMillis = engineGuardCheckIntervalMillis; + } + public String getGlobalName() { return globalName; } @@ -64,6 +99,14 @@ public void setGlobalName(String globalName) { this.globalName = globalName; } + public int getDefaultEngineGuardTimeoutMillis() { + return defaultEngineGuardTimeoutMillis; + } + + public void setDefaultEngineGuardTimeoutMillis(int defaultEngineGuardTimeoutMillis) { + this.defaultEngineGuardTimeoutMillis = defaultEngineGuardTimeoutMillis; + } + public int getResetEngineTimeoutSeconds() { return resetEngineTimeoutSeconds; } diff --git a/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java new file mode 100644 index 000000000..bef1d31c7 --- /dev/null +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEngineGuard.java @@ -0,0 +1,109 @@ +/* + * 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.engine; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interfaces.IJavetLogger; +import com.caoccao.javet.interop.V8Runtime; +import com.caoccao.javet.utils.JavetDateTimeUtils; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class JavetEngineGuard implements IJavetEngineGuard { + protected long timeoutMillis; + protected IJavetEngine iJavetEngine; + protected boolean quitting; + protected V8Runtime v8Runtime; + protected Future future; + + public JavetEngineGuard(IJavetEngine iJavetEngine, V8Runtime v8Runtime, long timeoutMills) { + Objects.requireNonNull(iJavetEngine); + this.iJavetEngine = iJavetEngine; + this.timeoutMillis = timeoutMills; + quitting = false; + this.v8Runtime = v8Runtime; + future = this.iJavetEngine.getConfig().getExecutorService().submit(this); + } + + @Override + public void cancel() { + quitting = true; + } + + @Override + public void close() throws JavetException { + cancel(); + if (!future.isDone() && !future.isCancelled()) { + future.cancel(true); + } + } + + @Override + public long getTimeoutMillis() { + return timeoutMillis; + } + + @Override + public void setTimeoutMillis(long timeoutSeconds) { + this.timeoutMillis = timeoutSeconds; + } + + public boolean isQuitting() { + return quitting; + } + + protected ZonedDateTime getUTCNow() { + return JavetDateTimeUtils.getUTCNow(); + } + + @Override + public void run() { + JavetEngineConfig config = iJavetEngine.getConfig(); + IJavetLogger logger = config.getJavetLogger(); + ZonedDateTime startZonedDateTime = getUTCNow(); + while (!isQuitting() && iJavetEngine.isActive()) { + ZonedDateTime currentZonedDateTime = getUTCNow(); + if (startZonedDateTime.plusNanos(TimeUnit.MILLISECONDS.toNanos(timeoutMillis)) + .isBefore(currentZonedDateTime)) { + try { + if (v8Runtime.isInUse()) { + // Javet only terminates the execution when V8 runtime is in use. + v8Runtime.terminateExecution(); + Duration duration = Duration.between(startZonedDateTime, currentZonedDateTime); + logger.logWarn("Execution was terminated after {0}ms.", duration.toMillis()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } finally { + break; + } + } else { + try { + Thread.sleep(config.getEngineGuardCheckIntervalMillis()); + } catch (InterruptedException e) { + // It's closed. + } + } + } + quitting = true; + } +} 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 85232cc2d..c3d8a868f 100644 --- a/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java +++ b/src/main/java/com/caoccao/javet/interop/engine/JavetEnginePool.java @@ -18,6 +18,7 @@ package com.caoccao.javet.interop.engine; import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interfaces.IJavetLogger; import com.caoccao.javet.interop.V8Host; import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.utils.JavetDateTimeUtils; @@ -26,6 +27,7 @@ import java.time.temporal.ChronoUnit; import java.util.LinkedList; import java.util.Objects; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class JavetEnginePool implements IJavetEnginePool, Runnable { @@ -79,7 +81,8 @@ public JavetEngineConfig getConfig() { @Override public IJavetEngine getEngine() { - config.getJavetLogger().debug("JavetEnginePool.getEngine() begins."); + IJavetLogger logger = config.getJavetLogger(); + logger.debug("JavetEnginePool.getEngine() begins."); JavetEngine engine = null; while (!quitting) { synchronized (internalLock) { @@ -99,12 +102,12 @@ public IJavetEngine getEngine() { try { TimeUnit.MILLISECONDS.sleep(config.getPoolDaemonCheckIntervalMillis()); } catch (InterruptedException e) { - config.getJavetLogger().logError(e, "Failed to sleep a while to wait for an idle engine."); + logger.logError(e, "Failed to sleep a while to wait for an idle engine."); } } JavetEngineUsage usage = engine.getUsage(); usage.increaseUsedCount(); - config.getJavetLogger().debug("JavetEnginePool.getEngine() ends."); + logger.debug("JavetEnginePool.getEngine() ends."); return engine; } @@ -119,22 +122,30 @@ protected ZonedDateTime getUTCNow() { return JavetDateTimeUtils.getUTCNow(); } + @Override public boolean isActive() { return active; } + @Override + public boolean isQuitting() { + return quitting; + } + @Override public void releaseEngine(IJavetEngine engine) { - config.getJavetLogger().debug("JavetEnginePool.releaseEngine() begins."); + IJavetLogger logger = config.getJavetLogger(); + logger.debug("JavetEnginePool.releaseEngine() begins."); synchronized (externalLock) { externalLock.notify(); } - config.getJavetLogger().debug("JavetEnginePool.releaseEngine() ends."); + logger.debug("JavetEnginePool.releaseEngine() ends."); } @Override public void run() { - config.getJavetLogger().debug("JavetEnginePool.run() begins."); + IJavetLogger logger = config.getJavetLogger(); + logger.debug("JavetEnginePool.run() begins."); while (!quitting) { synchronized (internalLock) { if (!activeEngineList.isEmpty()) { @@ -144,17 +155,19 @@ public void run() { if (engine.isActive()) { activeEngineList.push(engine); } else { - JavetEngineUsage usage = engine.getUsage(); - ZonedDateTime resetEngineZonedDateTime = usage.getLastActiveZonedDatetime() - .plus(config.getResetEngineTimeoutSeconds(), ChronoUnit.SECONDS); - if (usage.getEngineUsedCount() >= config.getMaxEngineUsedCount() || - resetEngineZonedDateTime.isBefore(getUTCNow())) { - try { - config.getJavetLogger().debug("JavetEnginePool reset engine begins."); - engine.resetIsolate(); - config.getJavetLogger().debug("JavetEnginePool reset engine ends."); - } catch (Exception e) { - config.getJavetLogger().logError(e, "Failed to reset idle engine."); + if (config.getMaxEngineUsedCount() > 0) { + JavetEngineUsage usage = engine.getUsage(); + ZonedDateTime resetEngineZonedDateTime = usage.getLastActiveZonedDatetime() + .plus(config.getResetEngineTimeoutSeconds(), ChronoUnit.SECONDS); + if (usage.getEngineUsedCount() >= config.getMaxEngineUsedCount() || + resetEngineZonedDateTime.isBefore(getUTCNow())) { + try { + logger.debug("JavetEnginePool reset engine begins."); + engine.resetContext(true); + logger.debug("JavetEnginePool reset engine ends."); + } catch (Exception e) { + logger.logError(e, "Failed to reset idle engine."); + } } } idleEngineList.push(engine); @@ -175,7 +188,7 @@ public void run() { try { engine.close(true); } catch (Throwable t) { - config.getJavetLogger().logError(t, "Failed to release idle engine."); + logger.logError(t, "Failed to release idle engine."); } } else { idleEngineList.push(engine); @@ -187,12 +200,12 @@ public void run() { try { externalLock.wait(config.getPoolDaemonCheckIntervalMillis()); } catch (InterruptedException e) { - config.getJavetLogger().logError(e, + logger.logError(e, "Failed to sleep a while to wait for next round in Javet engine pool daemon."); } } } - config.getJavetLogger().debug("JavetEnginePool daemon is quitting."); + logger.debug("JavetEnginePool daemon is quitting."); synchronized (internalLock) { if (!idleEngineList.isEmpty()) { final int idleEngineCount = getIdleEngineCount(); @@ -201,7 +214,7 @@ public void run() { try { engine.close(true); } catch (Throwable t) { - config.getJavetLogger().logError(t, "Failed to release idle engine."); + logger.logError(t, "Failed to release idle engine."); } } } @@ -212,39 +225,50 @@ public void run() { try { engine.close(true); } catch (Throwable t) { - config.getJavetLogger().logError(t, "Failed to release active engine."); + logger.logError(t, "Failed to release active engine."); } } } } - config.getJavetLogger().debug("JavetEnginePool.run() ends."); + logger.debug("JavetEnginePool.run() ends."); } protected void startDaemon() { - config.getJavetLogger().debug("JavetEnginePool.startDaemon() begins."); + IJavetLogger logger = config.getJavetLogger(); + logger.debug("JavetEnginePool.startDaemon() begins."); activeEngineList.clear(); idleEngineList.clear(); quitting = false; + config.setExecutorService(Executors.newCachedThreadPool()); daemonThread = new Thread(this); daemonThread.start(); active = true; - config.getJavetLogger().debug("JavetEnginePool.startDaemon() ends."); + logger.debug("JavetEnginePool.startDaemon() ends."); } protected void stopDaemon() { - config.getJavetLogger().debug("JavetEnginePool.stopDaemon() begins."); + IJavetLogger logger = config.getJavetLogger(); + logger.debug("JavetEnginePool.stopDaemon() begins."); quitting = true; + try { + config.getExecutorService().shutdown(); + config.getExecutorService().awaitTermination(config.getPoolShutdownTimeoutSeconds(), TimeUnit.SECONDS); + } catch (Exception e) { + logger.logError(e, e.getMessage()); + } finally { + config.setExecutorService(null); + } try { if (daemonThread != null) { daemonThread.join(); } } catch (Exception e) { - config.getJavetLogger().logError(e, e.getMessage()); + logger.logError(e, e.getMessage()); } finally { daemonThread = null; } active = false; quitting = false; - config.getJavetLogger().debug("JavetEnginePool.stopDaemon() ends."); + logger.debug("JavetEnginePool.stopDaemon() ends."); } } diff --git a/src/main/java/com/caoccao/javet/utils/JavetDefaultLogger.java b/src/main/java/com/caoccao/javet/utils/JavetDefaultLogger.java index 3b44b84c3..eba4a949b 100644 --- a/src/main/java/com/caoccao/javet/utils/JavetDefaultLogger.java +++ b/src/main/java/com/caoccao/javet/utils/JavetDefaultLogger.java @@ -2,6 +2,10 @@ import com.caoccao.javet.interfaces.IJavetLogger; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,7 +39,13 @@ public void error(String message) { @Override public void error(String message, Throwable cause) { logger.severe(message); - logger.severe(cause.getMessage()); + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + try (PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + cause.printStackTrace(printStream); + logger.severe(byteArrayOutputStream.toString(StandardCharsets.UTF_8.name())); + } + } catch (IOException e) { + } } @Override diff --git a/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java b/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java index 54a83ac3c..5ad496fbe 100644 --- a/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java +++ b/src/main/java/com/caoccao/javet/utils/V8ValueUtils.java @@ -17,19 +17,12 @@ package com.caoccao.javet.utils; -import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.values.V8Value; -import com.caoccao.javet.values.reference.V8ValueObject; -import com.caoccao.javet.values.virtual.V8VirtualList; import java.util.Arrays; import java.util.stream.Collectors; -@SuppressWarnings("unchecked") public final class V8ValueUtils { - public static final String FUNCTION_NEXT = "next"; - public static final String PROPERTY_DONE = "done"; - public static final String PROPERTY_VALUE = "value"; public static final String EMPTY = ""; private V8ValueUtils() { @@ -44,32 +37,6 @@ public static String concat(String delimiter, V8Value... v8Values) { } return String.join( delimiter, - Arrays.stream(v8Values).map(v8Value -> v8Value.toString()).collect(Collectors.toList())); - } - - public static V8VirtualList convertIteratorToIntegerList(V8ValueObject iterator) throws JavetException { - V8VirtualList resultList = new V8VirtualList<>(); - while (true) { - try (V8ValueObject next = iterator.invoke(FUNCTION_NEXT)) { - if (next.getBoolean(PROPERTY_DONE)) { - break; - } - resultList.add(next.getInteger(PROPERTY_VALUE)); - } - } - return resultList; - } - - public static V8VirtualList convertIteratorToV8ValueList(V8ValueObject iterator) throws JavetException { - V8VirtualList resultList = new V8VirtualList<>(); - while (true) { - try (V8ValueObject next = iterator.invoke(FUNCTION_NEXT)) { - if (next.getBoolean(PROPERTY_DONE)) { - break; - } - resultList.add(next.get(PROPERTY_VALUE)); - } - } - return resultList; + Arrays.stream(v8Values).map(V8Value::toString).collect(Collectors.toList())); } } diff --git a/src/main/java/com/caoccao/javet/utils/converters/IJavetConverter.java b/src/main/java/com/caoccao/javet/utils/converters/IJavetConverter.java index e6986a1a2..bcaffe597 100644 --- a/src/main/java/com/caoccao/javet/utils/converters/IJavetConverter.java +++ b/src/main/java/com/caoccao/javet/utils/converters/IJavetConverter.java @@ -21,8 +21,9 @@ import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.values.V8Value; +@SuppressWarnings("unchecked") public interface IJavetConverter { Object toObject(V8Value v8Value) throws JavetException; - V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetException; + T toV8Value(V8Runtime v8Runtime, Object object) throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/utils/converters/JavetObjectConverter.java b/src/main/java/com/caoccao/javet/utils/converters/JavetObjectConverter.java index b7a459544..f65320f6b 100644 --- a/src/main/java/com/caoccao/javet/utils/converters/JavetObjectConverter.java +++ b/src/main/java/com/caoccao/javet/utils/converters/JavetObjectConverter.java @@ -17,21 +17,34 @@ package com.caoccao.javet.utils.converters; +import com.caoccao.javet.entities.JavetEntityMap; import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.values.V8Value; +import com.caoccao.javet.values.V8ValueReferenceType; import com.caoccao.javet.values.primitive.*; import com.caoccao.javet.values.reference.*; -import com.caoccao.javet.values.virtual.V8VirtualList; import java.util.*; +@SuppressWarnings("unchecked") public class JavetObjectConverter extends JavetPrimitiveConverter { + public static final String PROPERTY_CONSTRUCTOR = "constructor"; + public static final String PROPERTY_NAME = "name"; + public JavetObjectConverter() { super(); } + protected Map createEntityMap() { + return new JavetEntityMap(); + } + + protected boolean isEntityMap(Object object) { + return object instanceof JavetEntityMap; + } + @Override public Object toObject(V8Value v8Value) throws JavetException { Object returnObject = super.toObject(v8Value); @@ -61,21 +74,76 @@ public Object toObject(V8Value v8Value) throws JavetException { } else if (v8Value instanceof V8ValueSet) { V8ValueSet v8ValueSet = (V8ValueSet) v8Value; HashSet set = new HashSet<>(); - try (V8VirtualList items = v8ValueSet.getKeys()) { - for (V8Value item : items) { - set.add(toObject(item)); + try (IV8ValueIterator iterator = v8ValueSet.getKeys()) { + while (true) { + try (V8Value key = iterator.getNext()) { + if (key == null) { + break; + } + set.add(toObject(key)); + } } } return set; - } else if (v8Value instanceof V8ValueMap || v8Value instanceof V8ValueObject) { + } else if (v8Value instanceof V8ValueMap) { + V8ValueMap v8ValueMap = (V8ValueMap) v8Value; + Map map = createEntityMap(); + try (IV8ValueIterator iterator = v8ValueMap.getEntries()) { + while (true) { + try (V8ValueArray entry = iterator.getNext()) { + if (entry == null) { + break; + } + try (V8Value key = entry.get(0); V8Value value = entry.get(1);) { + map.put(key.toString(), toObject(value)); + } + } + } + } + return map; + } else if (v8Value instanceof V8ValueTypedArray) { + V8ValueTypedArray v8ValueTypedArray = (V8ValueTypedArray) v8Value; + switch (v8ValueTypedArray.getType()) { + case V8ValueReferenceType.Int8Array: + case V8ValueReferenceType.Uint8Array: + case V8ValueReferenceType.Uint8ClampedArray: + return v8ValueTypedArray.toBytes(); + case V8ValueReferenceType.Int16Array: + case V8ValueReferenceType.Uint16Array: + return v8ValueTypedArray.toShorts(); + case V8ValueReferenceType.Int32Array: + case V8ValueReferenceType.Uint32Array: + return v8ValueTypedArray.toIntegers(); + case V8ValueReferenceType.Float32Array: + return v8ValueTypedArray.toFloats(); + case V8ValueReferenceType.Float64Array: + return v8ValueTypedArray.toDoubles(); + case V8ValueReferenceType.BigInt64Array: + case V8ValueReferenceType.BigUint64Array: + return v8ValueTypedArray.toLongs(); + default: + break; + } + } else if (v8Value instanceof V8ValueObject) { V8ValueObject v8ValueObject = (V8ValueObject) v8Value; Map map = new HashMap<>(); try (IV8ValueArray iV8ValueArray = v8ValueObject.getOwnPropertyNames()) { final int length = iV8ValueArray.getLength(); for (int i = 0; i < length; ++i) { try (V8Value key = iV8ValueArray.get(i)) { - try (V8Value value = v8ValueObject.get(key)) { - map.put(toObject(key).toString(), toObject(value)); + String keyString = key.toString(); + /* + Constructor is treated differently because it references to itself. + Otherwise stack overflow will take place. + */ + if (PROPERTY_CONSTRUCTOR.equals(keyString)) { + try (V8ValueObject v8ValueObjectValue = v8ValueObject.get(keyString)) { + map.put(keyString, v8ValueObjectValue.getString(PROPERTY_NAME)); + } + } else { + try (V8Value value = v8ValueObject.get(key)) { + map.put(keyString, toObject(value)); + } } } } @@ -86,12 +154,22 @@ public Object toObject(V8Value v8Value) throws JavetException { } @Override - public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetException { + public T toV8Value(V8Runtime v8Runtime, Object object) throws JavetException { V8Value v8Value = super.toV8Value(v8Runtime, object); - if (v8Value != null && !(v8Value instanceof V8ValueUndefined)) { - return v8Value; + if (v8Value != null && !(v8Value.isUndefined())) { + return (T) v8Value; } - if (object instanceof Map) { + if (isEntityMap(object)) { + V8ValueMap v8ValueMap = v8Runtime.createV8ValueMap(); + Map mapObject = (Map) object; + for (Object key : mapObject.keySet()) { + try (V8Value childV8Value = toV8Value(v8Runtime, mapObject.get(key))) { + String childStringKey = key instanceof String ? (String) key : key.toString(); + v8ValueMap.set(childStringKey, childV8Value); + } + } + v8Value = v8ValueMap; + } else if (object instanceof Map) { V8ValueObject v8ValueObject = v8Runtime.createV8ValueObject(); Map mapObject = (Map) object; for (Object key : mapObject.keySet()) { @@ -125,31 +203,41 @@ public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetExcepti } v8Value = v8ValueArray; } else if (object instanceof byte[]) { - // TODO To support byte[] + byte[] bytes = (byte[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.Int8Array, bytes.length); + v8ValueTypedArray.fromBytes(bytes); + v8Value = v8ValueTypedArray; } else if (object instanceof double[]) { - V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray(); - for (double item : (double[]) object) { - v8ValueArray.push(new V8ValueDouble(item)); - } - v8Value = v8ValueArray; + double[] doubles = (double[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.Float64Array, doubles.length); + v8ValueTypedArray.fromDoubles(doubles); + v8Value = v8ValueTypedArray; } else if (object instanceof float[]) { - V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray(); - for (float item : (float[]) object) { - v8ValueArray.push(new V8ValueDouble(item)); - } - v8Value = v8ValueArray; + float[] floats = (float[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.Float32Array, floats.length); + v8ValueTypedArray.fromFloats(floats); + v8Value = v8ValueTypedArray; } else if (object instanceof int[]) { - V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray(); - for (int item : (int[]) object) { - v8ValueArray.push(new V8ValueInteger(item)); - } - v8Value = v8ValueArray; + int[] integers = (int[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.Int32Array, integers.length); + v8ValueTypedArray.fromIntegers(integers); + v8Value = v8ValueTypedArray; } else if (object instanceof long[]) { - V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray(); - for (long item : (long[]) object) { - v8ValueArray.push(new V8ValueLong(item)); - } - v8Value = v8ValueArray; + long[] longs = (long[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.BigInt64Array, longs.length); + v8ValueTypedArray.fromLongs(longs); + v8Value = v8ValueTypedArray; + } else if (object instanceof short[]) { + short[] shorts = (short[]) object; + V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray( + V8ValueReferenceType.Int16Array, shorts.length); + v8ValueTypedArray.fromShorts(shorts); + v8Value = v8ValueTypedArray; } else if (object instanceof String[]) { V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray(); for (String item : (String[]) object) { @@ -165,6 +253,6 @@ public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetExcepti } v8Value = v8ValueArray; } - return v8Runtime.decorateV8Value(v8Value); + return (T) v8Runtime.decorateV8Value(v8Value); } } diff --git a/src/main/java/com/caoccao/javet/utils/converters/JavetPrimitiveConverter.java b/src/main/java/com/caoccao/javet/utils/converters/JavetPrimitiveConverter.java index 4eb030ba0..961735e7c 100644 --- a/src/main/java/com/caoccao/javet/utils/converters/JavetPrimitiveConverter.java +++ b/src/main/java/com/caoccao/javet/utils/converters/JavetPrimitiveConverter.java @@ -24,13 +24,14 @@ import java.time.ZonedDateTime; +@SuppressWarnings("unchecked") public class JavetPrimitiveConverter implements IJavetConverter { public JavetPrimitiveConverter() { } @Override public Object toObject(V8Value v8Value) throws JavetException { - if (v8Value == null || v8Value instanceof V8ValueNull || v8Value instanceof V8ValueUndefined) { + if (v8Value == null || v8Value.isNull() || v8Value.isUndefined()) { return null; } else if (v8Value instanceof V8ValuePrimitive) { return ((V8ValuePrimitive) v8Value).getValue(); @@ -39,10 +40,10 @@ public Object toObject(V8Value v8Value) throws JavetException { } @Override - public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetException { + public T toV8Value(V8Runtime v8Runtime, Object object) throws JavetException { V8Value v8Value = null; if (object == null) { - v8Value = new V8ValueNull(); + v8Value = v8Runtime.createV8ValueNull(); } else if (object instanceof V8Value) { v8Value = (V8Value) object; } else if (object instanceof Boolean) { @@ -55,6 +56,8 @@ public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetExcepti v8Value = new V8ValueInteger((Integer) object); } else if (object instanceof Long) { v8Value = new V8ValueLong((Long) object); + } else if (object instanceof Short) { + v8Value = new V8ValueInteger((Short) object); } else if (object instanceof String) { v8Value = new V8ValueString((String) object); } else if (object instanceof ZonedDateTime) { @@ -62,9 +65,9 @@ public V8Value toV8Value(V8Runtime v8Runtime, Object object) throws JavetExcepti } else if (object instanceof Byte) { v8Value = new V8ValueInteger((Byte) object); } else { - v8Value = new V8ValueUndefined(); + v8Value = v8Runtime.createV8ValueUndefined(); } - return v8Runtime.decorateV8Value(v8Value); + return (T) v8Runtime.decorateV8Value(v8Value); } } diff --git a/src/main/java/com/caoccao/javet/values/IV8Value.java b/src/main/java/com/caoccao/javet/values/IV8Value.java index 6a098a721..0699182b8 100644 --- a/src/main/java/com/caoccao/javet/values/IV8Value.java +++ b/src/main/java/com/caoccao/javet/values/IV8Value.java @@ -21,9 +21,69 @@ import com.caoccao.javet.interfaces.IJavetClosable; import com.caoccao.javet.interop.IV8Cloneable; import com.caoccao.javet.interop.V8Runtime; +import com.caoccao.javet.values.primitive.V8ValueDouble; +import com.caoccao.javet.values.primitive.V8ValueNull; +import com.caoccao.javet.values.primitive.V8ValueUndefined; +/** + * The interface V8 value. + */ public interface IV8Value extends IJavetClosable, IV8Cloneable { + /** + * Gets V8 runtime. + * + * @return the V8 runtime + */ V8Runtime getV8Runtime(); - T toClone() throws JavetException; + /** + * Equals. + *

+ * The behavior is different from JS behavior but is the same as Java behavior. + * + * @param v8Value the V8 value + * @return the boolean + * @throws JavetException the javet exception + */ + boolean equals(V8Value v8Value) throws JavetException; + + /** + * Is null. + * + * @return the boolean + */ + default boolean isNull() { + return this instanceof V8ValueNull; + } + + /** + * Is undefined. + * + * @return the boolean + */ + default boolean isUndefined() { + return this instanceof V8ValueUndefined; + } + + /** + * Same value. + *

+ * The behavior is different from JS behavior but is the same as Java behavior. + * + * @param v8Value the v 8 value + * @return the boolean + * @throws JavetException the javet exception + */ + boolean sameValue(V8Value v8Value) throws JavetException; + + /** + * Strict equals boolean. + *

+ * The behavior is different from JS behavior but is the same as Java behavior. + * + * @param v8Value the V8 value + * @return the boolean + * @throws JavetException the javet exception + */ + boolean strictEquals(V8Value v8Value) throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/values/V8Value.java b/src/main/java/com/caoccao/javet/values/V8Value.java index 3a872e399..38f8128e1 100644 --- a/src/main/java/com/caoccao/javet/values/V8Value.java +++ b/src/main/java/com/caoccao/javet/values/V8Value.java @@ -24,7 +24,7 @@ public abstract class V8Value implements IV8Value { protected V8Runtime v8Runtime; - public V8Value() { + protected V8Value() { v8Runtime = null; } @@ -46,6 +46,9 @@ public void close() throws JavetException { v8Runtime = null; } + @Override + public abstract boolean equals(V8Value v8Value) throws JavetException; + @Override public abstract T toClone() throws JavetException; @@ -61,5 +64,11 @@ public void setV8Runtime(V8Runtime v8Runtime) throws JavetException { this.v8Runtime.checkLock(); } + @Override + public abstract boolean sameValue(V8Value v8Value) throws JavetException; + + @Override + public abstract boolean strictEquals(V8Value v8Value) throws JavetException; + protected abstract void removeReference() throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/values/V8ValueReferenceType.java b/src/main/java/com/caoccao/javet/values/V8ValueReferenceType.java index 124a210fd..aebfcf92f 100644 --- a/src/main/java/com/caoccao/javet/values/V8ValueReferenceType.java +++ b/src/main/java/com/caoccao/javet/values/V8ValueReferenceType.java @@ -18,6 +18,7 @@ package com.caoccao.javet.values; public final class V8ValueReferenceType { + public static final int Invalid = 0; public static final int Object = 1; public static final int Error = 2; public static final int RegExp = 3; @@ -29,4 +30,18 @@ public final class V8ValueReferenceType { public static final int Set = 9; public static final int Array = 10; public static final int Function = 11; + public static final int Iterator = 12; + public static final int DataView = 30; + public static final int ArrayBuffer = 31; + public static final int Int8Array = 32; // -128 to 127 1 8-bit two's complement signed integer byte int8_t + public static final int Uint8Array = 33; // 0 to 255 1 8-bit unsigned integer octet uint8_t + public static final int Uint8ClampedArray = 34; // 0 to 255 1 8-bit unsigned integer (clamped) octet uint8_t + public static final int Int16Array = 35; // -32768 to 32767 2 16-bit two's complement signed integer short int16_t + public static final int Uint16Array = 36; // 0 to 65535 2 16-bit unsigned integer unsigned short uint16_t + public static final int Int32Array = 37; // -2147483648 to 2147483647 4 32-bit two's complement signed integer long int32_t + public static final int Uint32Array = 38; // 0 to 4294967295 4 32-bit unsigned integer unsigned long uint32_t + public static final int Float32Array = 39; // 1.2×10^-38 to 3.4×10^38 4 32-bit IEEE floating point number (7 significant digits e.g., 1.234567) unrestricted float float + public static final int Float64Array = 40; // 5.0×10^-324 to 1.8×10^308 8 64-bit IEEE floating point number (16 significant digits e.g., 1.23456789012345) unrestricted double double + public static final int BigInt64Array = 41; // -2^63 to 2^63-1 8 64-bit two's complement signed integer bigint int64_t (signed long long) + public static final int BigUint64Array = 42; // 0 to 2^64-1 8 64-bit unsigned integer bigint uint64_t (unsigned long long) } diff --git a/src/main/java/com/caoccao/javet/values/primitive/V8ValueDouble.java b/src/main/java/com/caoccao/javet/values/primitive/V8ValueDouble.java index c52c0d339..256db3eec 100644 --- a/src/main/java/com/caoccao/javet/values/primitive/V8ValueDouble.java +++ b/src/main/java/com/caoccao/javet/values/primitive/V8ValueDouble.java @@ -29,6 +29,18 @@ public V8ValueDouble(double value) { super(value); } + public boolean isFinite() { + return Double.isFinite(value); + } + + public boolean isInfinite() { + return Double.isInfinite(value); + } + + public boolean isNaN() { + return Double.isNaN(value); + } + @Override public V8ValueDouble toClone() throws JavetException { return v8Runtime.decorateV8Value(new V8ValueDouble(value)); diff --git a/src/main/java/com/caoccao/javet/values/primitive/V8ValueNull.java b/src/main/java/com/caoccao/javet/values/primitive/V8ValueNull.java index ea085556e..1f45caf89 100644 --- a/src/main/java/com/caoccao/javet/values/primitive/V8ValueNull.java +++ b/src/main/java/com/caoccao/javet/values/primitive/V8ValueNull.java @@ -33,13 +33,28 @@ public V8ValueNull() { protected void addReference() { } + @Override + public boolean equals(V8Value v8Value) { + return v8Value instanceof V8ValueNull; + } + @Override protected void removeReference() { } @Override - public V8ValueNull toClone() throws JavetException { - return v8Runtime.decorateV8Value(new V8ValueNull()); + public boolean sameValue(V8Value v8Value) { + return equals(v8Value); + } + + @Override + public boolean strictEquals(V8Value v8Value) { + return equals(v8Value); + } + + @Override + public V8ValueNull toClone() { + return v8Runtime.createV8ValueNull(); } @Override diff --git a/src/main/java/com/caoccao/javet/values/primitive/V8ValuePrimitive.java b/src/main/java/com/caoccao/javet/values/primitive/V8ValuePrimitive.java index e608cc39e..5cc0f766c 100644 --- a/src/main/java/com/caoccao/javet/values/primitive/V8ValuePrimitive.java +++ b/src/main/java/com/caoccao/javet/values/primitive/V8ValuePrimitive.java @@ -36,6 +36,17 @@ public V8ValuePrimitive(T value) { protected void addReference() { } + @Override + public boolean equals(V8Value v8Value) { + if (v8Value == null || !(v8Value instanceof V8ValuePrimitive)) { + return false; + } + if (v8Value.getClass() != this.getClass()) { + return false; + } + return getValue().equals(((V8ValuePrimitive) v8Value).getValue()); + } + public T getValue() { return value; } @@ -56,6 +67,16 @@ public boolean isPresent() { protected void removeReference() { } + @Override + public boolean sameValue(V8Value v8Value) { + return equals(v8Value); + } + + @Override + public boolean strictEquals(V8Value v8Value) { + return equals(v8Value); + } + @Override public String toString() { return isEmpty() ? null : value.toString(); diff --git a/src/main/java/com/caoccao/javet/values/primitive/V8ValueUndefined.java b/src/main/java/com/caoccao/javet/values/primitive/V8ValueUndefined.java index 0137ed695..8a338a69b 100644 --- a/src/main/java/com/caoccao/javet/values/primitive/V8ValueUndefined.java +++ b/src/main/java/com/caoccao/javet/values/primitive/V8ValueUndefined.java @@ -33,13 +33,28 @@ public V8ValueUndefined() { protected void addReference() { } + @Override + public boolean equals(V8Value v8Value) { + return v8Value instanceof V8ValueUndefined; + } + @Override protected void removeReference() { } + @Override + public boolean sameValue(V8Value v8Value) { + return equals(v8Value); + } + + @Override + public boolean strictEquals(V8Value v8Value) { + return equals(v8Value); + } + @Override public V8ValueUndefined toClone() throws JavetException { - return v8Runtime.decorateV8Value(new V8ValueUndefined()); + return v8Runtime.createV8ValueUndefined(); } @Override diff --git a/src/main/java/com/caoccao/javet/values/primitive/V8ValueUnknown.java b/src/main/java/com/caoccao/javet/values/primitive/V8ValueUnknown.java index 0a5b275b3..c4c80bd85 100644 --- a/src/main/java/com/caoccao/javet/values/primitive/V8ValueUnknown.java +++ b/src/main/java/com/caoccao/javet/values/primitive/V8ValueUnknown.java @@ -18,6 +18,7 @@ package com.caoccao.javet.values.primitive; import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8Value; @SuppressWarnings("unchecked") public final class V8ValueUnknown extends V8ValuePrimitive { 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 1920deed5..b9e0f730c 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueArray.java @@ -22,9 +22,11 @@ import com.caoccao.javet.values.primitive.*; import com.caoccao.javet.values.virtual.V8VirtualList; +import java.util.List; + @SuppressWarnings("unchecked") public interface IV8ValueArray extends IV8ValueObject { - V8VirtualList getKeys() throws JavetException; + List getKeys() throws JavetException; int getLength() throws JavetException; @@ -91,10 +93,10 @@ default int push(String value) throws JavetException { } default int pushNull() throws JavetException { - return push(new V8ValueNull()); + return push(getV8Runtime().createV8ValueNull()); } default int pushUndefined() throws JavetException { - return push(new V8ValueUndefined()); + return push(getV8Runtime().createV8ValueUndefined()); } } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueFunction.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueFunction.java index 4aa26e899..8e50d31da 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueFunction.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueFunction.java @@ -30,6 +30,8 @@ default T call(IV8ValueObject receiver, V8Value... v8Values) return call(receiver, true, v8Values); } + T callAsConstructor(V8Value... v8Values) throws JavetException; + default Boolean callBoolean(IV8ValueObject receiver, V8Value... v8Values) throws JavetException { return callObject(receiver, v8Values); } @@ -38,6 +40,11 @@ default Double callDouble(IV8ValueObject receiver, V8Value... v8Values) throws J return callObject(receiver, v8Values); } + default Float callFloat(IV8ValueObject receiver, V8Value... v8Values) throws JavetException { + Double result = callDouble(receiver, v8Values); + return result == null ? null : result.floatValue(); + } + default Integer callInteger(IV8ValueObject receiver, V8Value... v8Values) throws JavetException { return callObject(receiver, v8Values); } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueIterator.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueIterator.java new file mode 100644 index 000000000..06727e159 --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueIterator.java @@ -0,0 +1,36 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.interfaces.IJavetClosable; +import com.caoccao.javet.values.V8Value; + +/** + * The interface V8 value iterator. + * + * @param the type parameter + */ +@SuppressWarnings("unchecked") +public interface IV8ValueIterator extends IJavetClosable { + /** + * Gets next. + * + * @return the next + */ + T getNext(); +} diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueKeyContainer.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueKeyContainer.java index 40b5c5113..577ac766f 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueKeyContainer.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueKeyContainer.java @@ -25,29 +25,7 @@ @SuppressWarnings("unchecked") public interface IV8ValueKeyContainer extends IV8ValueObject { - List getKeys() throws JavetException; + IV8ValueIterator getKeys() throws JavetException; int getSize() throws JavetException; - - default boolean has(int value) throws JavetException { - return has(new V8ValueInteger(value)); - } - - default boolean has(long value) throws JavetException { - return has(new V8ValueLong(value)); - } - - default boolean has(String value) throws JavetException { - return has(new V8ValueString(value)); - } - - boolean has(V8Value value) throws JavetException; - - default boolean hasNull() throws JavetException { - return has(new V8ValueNull()); - } - - default boolean hasUndefined() throws JavetException { - return has(new V8ValueUndefined()); - } } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueMap.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueMap.java index d8d2fe514..c2376e2a0 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueMap.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueMap.java @@ -24,7 +24,7 @@ @SuppressWarnings("unchecked") public interface IV8ValueMap extends IV8ValueKeyContainer { - List getEntries() throws JavetException; + IV8ValueIterator getEntries() throws JavetException; - List getValues() throws JavetException; + IV8ValueIterator getValues() throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueObject.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueObject.java index 602094e3c..de052d456 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueObject.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueObject.java @@ -26,6 +26,28 @@ @SuppressWarnings("unchecked") public interface IV8ValueObject extends IV8ValueReference { + default boolean has(int value) throws JavetException { + return has(new V8ValueInteger(value)); + } + + default boolean has(long value) throws JavetException { + return has(new V8ValueLong(value)); + } + + default boolean has(String value) throws JavetException { + return has(new V8ValueString(value)); + } + + boolean has(V8Value value) throws JavetException; + + default boolean hasNull() throws JavetException { + return has(getV8Runtime().createV8ValueNull()); + } + + default boolean hasUndefined() throws JavetException { + return has(getV8Runtime().createV8ValueUndefined()); + } + T invoke(String functionName, boolean returnResult, V8Value... v8Values) throws JavetException; default T invoke(String functionName, V8Value... v8Values) throws JavetException { @@ -40,6 +62,11 @@ default Double invokeDouble(String functionName, V8Value... v8Values) throws Jav return invokeObject(functionName, v8Values); } + default Float invokeFloat(String functionName, V8Value... v8Values) throws JavetException { + Double result = invokeDouble(functionName, v8Values); + return result == null ? null : result.floatValue(); + } + default Integer invokeInteger(String functionName, V8Value... v8Values) throws JavetException { return invokeObject(functionName, v8Values); } @@ -82,11 +109,11 @@ default boolean delete(String key) throws JavetException { boolean delete(V8Value key) throws JavetException; default boolean deleteNull() throws JavetException { - return delete(new V8ValueNull()); + return delete(getV8Runtime().createV8ValueNull()); } default boolean deleteUndefined() throws JavetException { - return delete(new V8ValueUndefined()); + return delete(getV8Runtime().createV8ValueUndefined()); } default T get(int key) throws JavetException { @@ -115,6 +142,16 @@ default Double getDouble(String key) throws JavetException { return getObject(key); } + default Float getFloat(int key) throws JavetException { + Double result = getDouble(key); + return result == null ? null : result.floatValue(); + } + + default Float getFloat(String key) throws JavetException { + Double result = getDouble(key); + return result == null ? null : result.floatValue(); + } + default Integer getInteger(int key) throws JavetException { return getObject(key); } @@ -189,6 +226,16 @@ default Double getPropertyDouble(String key) throws JavetException { return getPropertyObject(key); } + default Float getPropertyFloat(int index) throws JavetException { + Double result = getPropertyDouble(index); + return result == null ? null : result.floatValue(); + } + + default Float getPropertyFloat(String key) throws JavetException { + Double result = getPropertyDouble(key); + return result == null ? null : result.floatValue(); + } + default Integer getPropertyInteger(int index) throws JavetException { return getPropertyObject(index); } @@ -285,6 +332,16 @@ default boolean set(String key, V8Value value) throws JavetException { boolean set(V8Value key, V8Value value) throws JavetException; + /** + * Sets function by name and callback context. + *

+ * It is for creating a Java code based function in V8. + * + * @param functionName the function name + * @param javetCallbackContext the javet callback context + * @return true: function is set, false: function is not set + * @throws JavetException the javet exception + */ default boolean setFunction(String functionName, JavetCallbackContext javetCallbackContext) throws JavetException { V8ValueFunction v8ValueFunction = getV8Runtime().createV8ValueFunction(javetCallbackContext); boolean success = set(functionName, v8ValueFunction); @@ -292,12 +349,34 @@ default boolean setFunction(String functionName, JavetCallbackContext javetCallb return success; } + /** + * Sets function by name and string. + *

+ * It is for creating a string based function in V8. + *

+ * JS equivalent: + * + * obj.func = function(arg1, arg2) { ... }; + * + * + * @param functionName the function name + * @param codeString the code string + * @return true: function is set, false: function is not set + * @throws JavetException the javet exception + */ + default boolean setFunction(String functionName, String codeString) throws JavetException { + V8ValueFunction v8ValueFunction = getV8Runtime().getExecutor(codeString).execute(); + boolean success = set(functionName, v8ValueFunction); + v8ValueFunction.setWeak(); + return success; + } + default boolean setNull(int key) throws JavetException { - return set(new V8ValueInteger(key), new V8ValueNull()); + return set(new V8ValueInteger(key), getV8Runtime().createV8ValueNull()); } default boolean setNull(String key) throws JavetException { - return set(new V8ValueString(key), new V8ValueNull()); + return set(new V8ValueString(key), getV8Runtime().createV8ValueNull()); } default boolean setProperty(int key, V8Value value) throws JavetException { @@ -311,28 +390,36 @@ default boolean setProperty(String key, V8Value value) throws JavetException { boolean setProperty(V8Value key, V8Value value) throws JavetException; default boolean setPropertyNull(int key) throws JavetException { - return setProperty(new V8ValueInteger(key), new V8ValueNull()); + return setProperty(new V8ValueInteger(key), getV8Runtime().createV8ValueNull()); } default boolean setPropertyNull(String key) throws JavetException { - return setProperty(new V8ValueString(key), new V8ValueNull()); + return setProperty(new V8ValueString(key), getV8Runtime().createV8ValueNull()); } default boolean setPropertyUndefined(int key) throws JavetException { - return setProperty(new V8ValueInteger(key), new V8ValueUndefined()); + return setProperty(new V8ValueInteger(key), getV8Runtime().createV8ValueUndefined()); } default boolean setPropertyUndefined(String key) throws JavetException { - return setProperty(new V8ValueString(key), new V8ValueUndefined()); + return setProperty(new V8ValueString(key), getV8Runtime().createV8ValueUndefined()); } default boolean setUndefined(int key) throws JavetException { - return set(new V8ValueInteger(key), new V8ValueUndefined()); + return set(new V8ValueInteger(key), getV8Runtime().createV8ValueUndefined()); } default boolean setUndefined(String key) throws JavetException { - return set(new V8ValueString(key), new V8ValueUndefined()); + return set(new V8ValueString(key), getV8Runtime().createV8ValueUndefined()); } + /** + * To json string. + *

+ * JS equivalent: + * JSON.stringify(obj); + * + * @return the string + */ String toJsonString(); } 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 43e0dc65f..f2562fa9e 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueReference.java @@ -28,6 +28,18 @@ public interface IV8ValueReference extends IV8Value { long getHandle(); + /** + * Returns the identity hash for this object. The current implementation + * uses an inline property on the object to store the identity hash. + *

+ * The return value will never be 0. Also, it is not guaranteed to be + * unique. + * + * @return the identity hash + * @throws JavetException the javet exception + */ + int getIdentityHash() throws JavetException; + int getType(); boolean isWeak() throws JavetException; diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueSet.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueSet.java index 47043ff42..062f77b88 100644 --- a/src/main/java/com/caoccao/javet/values/reference/IV8ValueSet.java +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueSet.java @@ -19,8 +19,6 @@ import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.values.V8Value; -import com.caoccao.javet.values.primitive.V8ValueNull; -import com.caoccao.javet.values.primitive.V8ValueUndefined; import com.caoccao.javet.values.primitive.V8ValueInteger; import com.caoccao.javet.values.primitive.V8ValueLong; import com.caoccao.javet.values.primitive.V8ValueString; @@ -43,10 +41,12 @@ default void add(String value) throws JavetException { void add(V8Value key) throws JavetException; default void addNull() throws JavetException { - add(new V8ValueNull()); + add(getV8Runtime().createV8ValueNull()); } default void addUndefined() throws JavetException { - add(new V8ValueUndefined()); + add(getV8Runtime().createV8ValueUndefined()); } + + IV8ValueIterator getEntries() throws JavetException; } diff --git a/src/main/java/com/caoccao/javet/values/reference/IV8ValueTypedArray.java b/src/main/java/com/caoccao/javet/values/reference/IV8ValueTypedArray.java new file mode 100644 index 000000000..4f7cb9a75 --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/IV8ValueTypedArray.java @@ -0,0 +1,34 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.exceptions.JavetException; + +public interface IV8ValueTypedArray extends IV8ValueObject { + V8ValueArrayBuffer getBuffer() throws JavetException; + + int getByteLength() throws JavetException; + + int getByteOffset() throws JavetException; + + int getLength() throws JavetException; + + int getSizeInBytes(); + + boolean isValid(); +} diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueArguments.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueArguments.java index 88436a9bc..2d1d95a35 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueArguments.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueArguments.java @@ -20,7 +20,7 @@ import com.caoccao.javet.values.V8ValueReferenceType; public class V8ValueArguments extends V8ValueArray { - public V8ValueArguments(long handle) { + V8ValueArguments(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueArray.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueArray.java index 374c30d24..ebcd3c0d1 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueArray.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueArray.java @@ -21,17 +21,21 @@ import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.V8ValueReferenceType; import com.caoccao.javet.values.primitive.V8ValueInteger; -import com.caoccao.javet.utils.V8ValueUtils; -import com.caoccao.javet.values.virtual.V8VirtualList; + +import java.util.ArrayList; +import java.util.List; @SuppressWarnings("unchecked") public class V8ValueArray extends V8ValueObject implements IV8ValueArray { + public static final String FUNCTION_NEXT = "next"; + public static final String PROPERTY_DONE = "done"; + public static final String PROPERTY_VALUE = "value"; public static final String FUNCTION_KEYS = "keys"; public static final String FUNCTION_POP = "pop"; public static final String FUNCTION_PUSH = "push"; - public V8ValueArray(long handle) { + V8ValueArray(long handle) { super(handle); } @@ -42,16 +46,24 @@ public T get(int index) throws JavetException { } @Override - public V8VirtualList getKeys() throws JavetException { + public List getKeys() throws JavetException { checkV8Runtime(); - try (V8ValueObject arrayIterator = invoke(FUNCTION_KEYS)) { - return V8ValueUtils.convertIteratorToIntegerList(arrayIterator); + try (V8ValueObject iterator = invoke(FUNCTION_KEYS)) { + List keys = new ArrayList<>(); + while (true) { + try (V8ValueObject next = iterator.invoke(FUNCTION_NEXT)) { + if (next.getBoolean(PROPERTY_DONE)) { + break; + } + keys.add(next.getInteger(PROPERTY_VALUE)); + } + } + return keys; } } @Override - public int getLength() - throws JavetException { + public int getLength() throws JavetException { checkV8Runtime(); return v8Runtime.getLength(this); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueArrayBuffer.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueArrayBuffer.java new file mode 100644 index 000000000..0dff22f4c --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueArrayBuffer.java @@ -0,0 +1,149 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8ValueReferenceType; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +@SuppressWarnings("unchecked") +public class V8ValueArrayBuffer extends V8ValueObject { + + public static final String PROPERTY_BYTE_LENGTH = "byteLength"; + public static final int BYTE_LENGTH_1 = 1; + public static final int BYTE_LENGTH_2 = 2; + public static final int BYTE_LENGTH_3 = 3; + + protected ByteBuffer byteBuffer; + protected ByteOrder byteOrder; + + protected V8ValueArrayBuffer(long handle, ByteBuffer byteBuffer) { + super(handle); + this.byteBuffer = byteBuffer; + byteOrder = ByteOrder.nativeOrder(); + } + + public ByteOrder getByteOrder() { + return byteOrder; + } + + public void setByteOrder(ByteOrder byteOrder) { + Objects.requireNonNull(byteOrder); + this.byteOrder = byteOrder; + } + + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + public int getByteLength() throws JavetException { + return getInteger(PROPERTY_BYTE_LENGTH); + } + + @Override + public int getType() { + return V8ValueReferenceType.ArrayBuffer; + } + + public boolean fromBytes(byte[] bytes) { + if (bytes != null && bytes.length > 0 && bytes.length == byteBuffer.capacity()) { + byteBuffer.put(bytes); + return true; + } + return false; + } + + public boolean fromDoubles(double[] doubles) { + if (doubles != null && doubles.length > 0 && doubles.length == byteBuffer.capacity() >> BYTE_LENGTH_3) { + byteBuffer.order(byteOrder).asDoubleBuffer().put(doubles); + return true; + } + return false; + } + + public boolean fromFloats(float[] floats) { + if (floats != null && floats.length > 0 && floats.length == byteBuffer.capacity() >> BYTE_LENGTH_2) { + byteBuffer.order(byteOrder).asFloatBuffer().put(floats); + return true; + } + return false; + } + + public boolean fromIntegers(int[] integers) { + if (integers != null && integers.length > 0 && integers.length == byteBuffer.capacity() >> BYTE_LENGTH_2) { + byteBuffer.order(byteOrder).asIntBuffer().put(integers); + return true; + } + return false; + } + + public boolean fromLongs(long[] longs) { + if (longs != null && longs.length > 0 && longs.length == byteBuffer.capacity() >> BYTE_LENGTH_3) { + byteBuffer.order(byteOrder).asLongBuffer().put(longs); + return true; + } + return false; + } + + public boolean fromShorts(short[] shorts) { + if (shorts != null && shorts.length > 0 && shorts.length == byteBuffer.capacity() >> BYTE_LENGTH_1) { + byteBuffer.order(byteOrder).asShortBuffer().put(shorts); + return true; + } + return false; + } + + public byte[] toBytes() { + byte[] bytes = new byte[byteBuffer.capacity()]; + byteBuffer.get(bytes); + return bytes; + } + + public double[] toDoubles() { + double[] doubles = new double[byteBuffer.capacity() >> BYTE_LENGTH_3]; + byteBuffer.order(byteOrder).asDoubleBuffer().get(doubles); + return doubles; + } + + public float[] toFloats() { + float[] floats = new float[byteBuffer.capacity() >> BYTE_LENGTH_2]; + byteBuffer.order(byteOrder).asFloatBuffer().get(floats); + return floats; + } + + public int[] toIntegers() { + int[] integers = new int[byteBuffer.capacity() >> BYTE_LENGTH_2]; + byteBuffer.order(byteOrder).asIntBuffer().get(integers); + return integers; + } + + public long[] toLongs() { + long[] longs = new long[byteBuffer.capacity() >> BYTE_LENGTH_3]; + byteBuffer.order(byteOrder).asLongBuffer().get(longs); + return longs; + } + + public short[] toShorts() { + short[] shorts = new short[byteBuffer.capacity() >> BYTE_LENGTH_1]; + byteBuffer.order(byteOrder).asShortBuffer().get(shorts); + return shorts; + } +} diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueDataView.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueDataView.java new file mode 100644 index 000000000..bdcfb05da --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueDataView.java @@ -0,0 +1,172 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8ValueReferenceType; +import com.caoccao.javet.values.primitive.V8ValueBoolean; +import com.caoccao.javet.values.primitive.V8ValueDouble; +import com.caoccao.javet.values.primitive.V8ValueInteger; +import com.caoccao.javet.values.primitive.V8ValueLong; + +public class V8ValueDataView extends V8ValueObject { + /** + * The constant PROPERTY_BYTE_LENGTH. + */ + public static final String PROPERTY_BYTE_LENGTH = "byteLength"; + /** + * The constant PROPERTY_BUFFER. + */ + public static final String PROPERTY_BUFFER = "buffer"; + /** + * The constant PROPERTY_BYTE_OFFSET. + */ + public static final String PROPERTY_BYTE_OFFSET = "byteOffset"; + public static final String FUNCTION_GET_BIG_INT_64 = "getBigInt64"; + public static final String FUNCTION_GET_FLOAT_32 = "getFloat32"; + public static final String FUNCTION_GET_FLOAT_64 = "getFloat64"; + public static final String FUNCTION_GET_INT_8 = "getInt8"; + public static final String FUNCTION_GET_INT_16 = "getInt16"; + public static final String FUNCTION_GET_INT_32 = "getInt32"; + public static final String FUNCTION_SET_BIG_INT_64 = "setBigInt64"; + public static final String FUNCTION_SET_FLOAT_32 = "setFloat32"; + public static final String FUNCTION_SET_FLOAT_64 = "setFloat64"; + public static final String FUNCTION_SET_INT_8 = "setInt8"; + public static final String FUNCTION_SET_INT_16 = "setInt16"; + public static final String FUNCTION_SET_INT_32 = "setInt32"; + + V8ValueDataView(long handle) { + super(handle); + } + + public long getBigInt64(int byteOffset) throws JavetException { + return getBigInt64(byteOffset, true); + } + + public long getBigInt64(int byteOffset, boolean littleEndian) throws JavetException { + return invokeLong(FUNCTION_GET_BIG_INT_64, + new V8ValueInteger(byteOffset), new V8ValueBoolean(littleEndian)).longValue(); + } + + public V8ValueArrayBuffer getBuffer() throws JavetException { + return get(PROPERTY_BUFFER); + } + + public int getByteLength() throws JavetException { + return getInteger(PROPERTY_BYTE_LENGTH); + } + + public int getByteOffset() throws JavetException { + return getInteger(PROPERTY_BYTE_OFFSET); + } + + public float getFloat32(int byteOffset) throws JavetException { + return getFloat32(byteOffset, true); + } + + public float getFloat32(int byteOffset, boolean littleEndian) throws JavetException { + return invokeDouble(FUNCTION_GET_FLOAT_32, + new V8ValueInteger(byteOffset), new V8ValueBoolean(littleEndian)).floatValue(); + } + + public double getFloat64(int byteOffset) throws JavetException { + return getFloat64(byteOffset, true); + } + + public double getFloat64(int byteOffset, boolean littleEndian) throws JavetException { + return invokeDouble(FUNCTION_GET_FLOAT_64, + new V8ValueInteger(byteOffset), new V8ValueBoolean(littleEndian)).doubleValue(); + } + + public short getInt16(int byteOffset) throws JavetException { + return getInt16(byteOffset, true); + } + + public short getInt16(int byteOffset, boolean littleEndian) throws JavetException { + return invokeInteger(FUNCTION_GET_INT_16, + new V8ValueInteger(byteOffset), new V8ValueBoolean(littleEndian)).shortValue(); + } + + public int getInt32(int byteOffset) throws JavetException { + return getInt32(byteOffset, true); + } + + public int getInt32(int byteOffset, boolean littleEndian) throws JavetException { + return invokeInteger(FUNCTION_GET_INT_32, + new V8ValueInteger(byteOffset), new V8ValueBoolean(littleEndian)).intValue(); + } + + public byte getInt8(int byteOffset) throws JavetException { + return invokeInteger(FUNCTION_GET_INT_8, + new V8ValueInteger(byteOffset)).byteValue(); + } + + @Override + public int getType() { + return V8ValueReferenceType.DataView; + } + + public void setBigInt64(int byteOffset, long value) throws JavetException { + setBigInt64(byteOffset, value, true); + } + + public void setBigInt64(int byteOffset, long value, boolean littleEndian) throws JavetException { + invokeVoid(FUNCTION_SET_BIG_INT_64, + new V8ValueInteger(byteOffset), new V8ValueLong(value), new V8ValueBoolean(littleEndian)); + } + + public void setFloat32(int byteOffset, float value) throws JavetException { + setFloat32(byteOffset, value, true); + } + + public void setFloat32(int byteOffset, float value, boolean littleEndian) throws JavetException { + invokeVoid(FUNCTION_SET_FLOAT_32, + new V8ValueInteger(byteOffset), new V8ValueDouble(value), new V8ValueBoolean(littleEndian)); + } + + public void setFloat64(int byteOffset, double value) throws JavetException { + setFloat64(byteOffset, value, true); + } + + public void setFloat64(int byteOffset, double value, boolean littleEndian) throws JavetException { + invokeVoid(FUNCTION_SET_FLOAT_64, + new V8ValueInteger(byteOffset), new V8ValueDouble(value), new V8ValueBoolean(littleEndian)); + } + + public void setInt32(int byteOffset, int value) throws JavetException { + setInt32(byteOffset, value, true); + } + + public void setInt32(int byteOffset, int value, boolean littleEndian) throws JavetException { + invokeVoid(FUNCTION_SET_INT_32, + new V8ValueInteger(byteOffset), new V8ValueInteger(value), new V8ValueBoolean(littleEndian)); + } + + public void setInt16(int byteOffset, short value) throws JavetException { + setInt16(byteOffset, value, true); + } + + public void setInt16(int byteOffset, short value, boolean littleEndian) throws JavetException { + invokeVoid(FUNCTION_SET_INT_16, + new V8ValueInteger(byteOffset), new V8ValueInteger(value), new V8ValueBoolean(littleEndian)); + } + + public void setInt8(int byteOffset, byte value) throws JavetException { + invokeVoid(FUNCTION_SET_INT_8, new V8ValueInteger(byteOffset), new V8ValueInteger(value)); + } +} diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueError.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueError.java index 75035f590..dce0af158 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueError.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueError.java @@ -25,7 +25,7 @@ public class V8ValueError extends V8ValueObject { public static final String STACK = "stack"; public static final String MESSAGE = "message"; - public V8ValueError(long handle) { + V8ValueError(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueFunction.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueFunction.java index 2526d4ee0..a48469b13 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueFunction.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueFunction.java @@ -43,7 +43,7 @@ public class V8ValueFunction extends V8ValueObject implements IV8ValueFunction { */ protected JavetCallbackContext javetCallbackContext; - public V8ValueFunction(long handle) { + V8ValueFunction(long handle) { super(handle); javetCallbackContext = null; } @@ -70,6 +70,12 @@ public T call(IV8ValueObject receiver, boolean returnResult, return v8Runtime.call(this, receiver, returnResult, v8Values); } + @Override + public T callAsConstructor(V8Value... v8Values) throws JavetException { + checkV8Runtime(); + return v8Runtime.callAsConstructor(this, v8Values); + } + @Override public void close(boolean forceClose) throws JavetException { // V8 lock free @@ -194,6 +200,6 @@ public V8Value receiveCallback(V8Value thisObject, V8ValueArray args) throws Thr JavetResourceUtils.safeClose(values); } } - return v8Runtime.decorateV8Value(new V8ValueUndefined()); + return v8Runtime.createV8ValueUndefined(); } } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueGlobalObject.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueGlobalObject.java index fb4969877..df8b1db1e 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueGlobalObject.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueGlobalObject.java @@ -19,9 +19,21 @@ import com.caoccao.javet.exceptions.JavetException; +/** + * The type V8 value global object is a special object. + * 1. It lives as long as V8 runtime lives. + * 2. It does not have reference count. + * 3. It cannot be set to weak. + * 4. Its clone is itself. + */ @SuppressWarnings("unchecked") public final class V8ValueGlobalObject extends V8ValueObject { - public V8ValueGlobalObject(long handle) { + /** + * Instantiates a new V8 value global object. + * + * @param handle the handle + */ + V8ValueGlobalObject(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueIterator.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueIterator.java new file mode 100644 index 000000000..ede622305 --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueIterator.java @@ -0,0 +1,49 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8Value; +import com.caoccao.javet.values.V8ValueReferenceType; + +@SuppressWarnings("unchecked") +public class V8ValueIterator extends V8ValueObject implements IV8ValueIterator { + public static final String FUNCTION_NEXT = "next"; + public static final String PROPERTY_DONE = "done"; + public static final String PROPERTY_VALUE = "value"; + + public V8ValueIterator(long handle) { + super(handle); + } + + @Override + public int getType() { + return V8ValueReferenceType.Iterator; + } + + @Override + public T getNext() { + try (V8ValueObject next = invoke(FUNCTION_NEXT)) { + if (!next.getBoolean(PROPERTY_DONE)) { + return next.get(PROPERTY_VALUE); + } + } catch (JavetException javetException) { + } + return null; + } +} diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueMap.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueMap.java index 244093e46..d306dedb0 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueMap.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueMap.java @@ -18,12 +18,8 @@ package com.caoccao.javet.values.reference; import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.utils.V8ValueUtils; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.V8ValueReferenceType; -import com.caoccao.javet.values.virtual.V8VirtualList; - -import java.util.List; @SuppressWarnings("unchecked") public class V8ValueMap extends V8ValueObject implements IV8ValueMap { @@ -32,24 +28,20 @@ public class V8ValueMap extends V8ValueObject implements IV8ValueMap { public static final String FUNCTION_VALUES = "values"; public static final String FUNCTION_ENTRIES = "entries"; - public V8ValueMap(long handle) { + V8ValueMap(long handle) { super(handle); } @Override - public V8VirtualList getEntries() throws JavetException { + public IV8ValueIterator getEntries() throws JavetException { checkV8Runtime(); - try (V8ValueObject mapIterator = invoke(FUNCTION_ENTRIES)) { - return V8ValueUtils.convertIteratorToV8ValueList(mapIterator); - } + return invoke(FUNCTION_ENTRIES); } @Override - public V8VirtualList getKeys() throws JavetException { + public IV8ValueIterator getKeys() throws JavetException { checkV8Runtime(); - try (V8ValueObject mapIterator = invoke(FUNCTION_KEYS)) { - return V8ValueUtils.convertIteratorToV8ValueList(mapIterator); - } + return invoke(FUNCTION_KEYS); } @Override @@ -64,16 +56,8 @@ public int getType() { } @Override - public List getValues() throws JavetException { - checkV8Runtime(); - try (V8ValueObject mapIterator = invoke(FUNCTION_VALUES)) { - return V8ValueUtils.convertIteratorToV8ValueList(mapIterator); - } - } - - @Override - public boolean has(V8Value value) throws JavetException { + public IV8ValueIterator getValues() throws JavetException { checkV8Runtime(); - return v8Runtime.has(this, value); + return invoke(FUNCTION_VALUES); } } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueObject.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueObject.java index c1fbe6fa1..fbe65c597 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueObject.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueObject.java @@ -17,7 +17,7 @@ package com.caoccao.javet.values.reference; -import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.exceptions.*; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.V8ValueReferenceType; @@ -44,14 +44,14 @@ public T get(V8Value key) throws JavetException { } @Override - public int getType() { - return V8ValueReferenceType.Object; + public int getIdentityHash() throws JavetException { + checkV8Runtime(); + return v8Runtime.getIdentityHash(this); } @Override - public boolean hasOwnProperty(V8Value key) throws JavetException { - checkV8Runtime(); - return v8Runtime.hasOwnProperty(this, key); + public int getType() { + return V8ValueReferenceType.Object; } @Override @@ -73,6 +73,18 @@ public T getProperty(V8Value key) return v8Runtime.getProperty(this, key); } + @Override + public boolean has(V8Value value) throws JavetException { + checkV8Runtime(); + return v8Runtime.has(this, value); + } + + @Override + public boolean hasOwnProperty(V8Value key) throws JavetException { + checkV8Runtime(); + return v8Runtime.hasOwnProperty(this, key); + } + @Override public T invoke(String functionName, boolean returnResult, V8Value... v8Values) throws JavetException { diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValuePromise.java b/src/main/java/com/caoccao/javet/values/reference/V8ValuePromise.java index 99ac751a5..254690364 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValuePromise.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValuePromise.java @@ -21,7 +21,7 @@ public class V8ValuePromise extends V8ValueObject { - public V8ValuePromise(long handle) { + V8ValuePromise(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueProxy.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueProxy.java index e6ff75956..9c0031262 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueProxy.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueProxy.java @@ -21,7 +21,7 @@ public class V8ValueProxy extends V8ValueObject { - public V8ValueProxy(long handle) { + V8ValueProxy(long handle) { super(handle); } 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 524a9c3a8..78391f8c9 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueReference.java @@ -20,12 +20,13 @@ import com.caoccao.javet.exceptions.*; import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.values.V8Value; +import com.caoccao.javet.values.primitive.V8ValuePrimitive; public abstract class V8ValueReference extends V8Value implements IV8ValueReference { protected long handle; protected boolean weak; - public V8ValueReference(long handle) { + V8ValueReference(long handle) { super(); this.handle = handle; weak = false; @@ -72,6 +73,21 @@ public void close(boolean forceClose) throws JavetException { } } + @Override + public boolean equals(V8Value v8Value) throws JavetException { + if (v8Value == null || !(v8Value instanceof V8ValueReference)) { + return false; + } + if (v8Value.getClass() != this.getClass()) { + return false; + } + V8ValueReference v8ValueReference = (V8ValueReference) v8Value; + if (getHandle() == v8ValueReference.getHandle()) { + return true; + } + return v8Runtime.equals(this, v8ValueReference); + } + @Override public abstract int getType(); @@ -80,6 +96,9 @@ public long getHandle() { return handle; } + @Override + public abstract int getIdentityHash() throws JavetException; + @Override public boolean isWeak() throws JavetException { // V8 lock free @@ -114,6 +133,36 @@ public void setWeak() throws JavetException { weak = true; } + @Override + public boolean sameValue(V8Value v8Value) throws JavetException { + if (v8Value == null || !(v8Value instanceof V8ValueReference)) { + return false; + } + if (v8Value.getClass() != this.getClass()) { + return false; + } + V8ValueReference v8ValueReference = (V8ValueReference) v8Value; + if (getHandle() == v8ValueReference.getHandle()) { + return true; + } + return v8Runtime.sameValue(this, v8ValueReference); + } + + @Override + public boolean strictEquals(V8Value v8Value) throws JavetException { + if (v8Value == null || !(v8Value instanceof V8ValueReference)) { + return false; + } + if (v8Value.getClass() != this.getClass()) { + return false; + } + V8ValueReference v8ValueReference = (V8ValueReference) v8Value; + if (getHandle() == v8ValueReference.getHandle()) { + return true; + } + return v8Runtime.strictEquals(this, v8ValueReference); + } + @Override public String toProtoString() { try { diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueRegExp.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueRegExp.java index 18a910a77..6e6302ecf 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueRegExp.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueRegExp.java @@ -21,7 +21,7 @@ public class V8ValueRegExp extends V8ValueObject { - public V8ValueRegExp(long handle) { + V8ValueRegExp(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueSet.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueSet.java index a24e62c69..5f434e6c9 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueSet.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueSet.java @@ -18,17 +18,16 @@ package com.caoccao.javet.values.reference; import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.utils.V8ValueUtils; import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.V8ValueReferenceType; -import com.caoccao.javet.values.virtual.V8VirtualList; @SuppressWarnings("unchecked") public class V8ValueSet extends V8ValueObject implements IV8ValueSet { + public static final String FUNCTION_ENTRIES = "entries"; public static final String FUNCTION_KEYS = "keys"; - public V8ValueSet(long handle) { + V8ValueSet(long handle) { super(handle); } @@ -38,17 +37,21 @@ public void add(V8Value key) throws JavetException { v8Runtime.add(this, key); } + @Override + public IV8ValueIterator getEntries() throws JavetException { + checkV8Runtime(); + return invoke(FUNCTION_ENTRIES); + } + @Override public int getType() { return V8ValueReferenceType.Set; } @Override - public V8VirtualList getKeys() throws JavetException { + public IV8ValueIterator getKeys() throws JavetException { checkV8Runtime(); - try (V8ValueObject setIterator = invoke(FUNCTION_KEYS)) { - return V8ValueUtils.convertIteratorToV8ValueList(setIterator); - } + return invoke(FUNCTION_KEYS); } @Override @@ -56,10 +59,4 @@ public int getSize() throws JavetException { checkV8Runtime(); return v8Runtime.getSize(this); } - - @Override - public boolean has(V8Value value) throws JavetException { - checkV8Runtime(); - return v8Runtime.has(this, value); - } } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueSymbol.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueSymbol.java index a46cbcbc0..7c8e32933 100644 --- a/src/main/java/com/caoccao/javet/values/reference/V8ValueSymbol.java +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueSymbol.java @@ -27,7 +27,7 @@ public class V8ValueSymbol extends V8ValueObject { public static final String DESCRIPTION = "description"; public static final String SYMBOL_0 = "Symbol({0})"; - public V8ValueSymbol(long handle) { + V8ValueSymbol(long handle) { super(handle); } diff --git a/src/main/java/com/caoccao/javet/values/reference/V8ValueTypedArray.java b/src/main/java/com/caoccao/javet/values/reference/V8ValueTypedArray.java new file mode 100644 index 000000000..9609ad931 --- /dev/null +++ b/src/main/java/com/caoccao/javet/values/reference/V8ValueTypedArray.java @@ -0,0 +1,455 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8ValueReferenceType; + +/** + * The type V8 value typed array. + * The typical way of manipulating the typed array is as following. + * 1. Get array buffer and apply try-with-resource. + * 2. Create the value array by length. + * 3. Fill the value array. + * E.g.: + * + * try (V8ValueArrayBuffer v8ValueArrayBuffer = v8ValueTypedArray.getArrayBuffer()) { + * long[] longs = new long[v8ValueArrayBuffer.getLength()]; + * v8ValueArrayBuffer.toLongs(longs); + * // ... + * } + * + * Or, play with ByteBuffer directly. + * 1. Get array buffer and apply try-with-resource. + * 2. Get native ByteBuffer. + * 3. Set order of the native ByteBuffer to native order. + * 4. Get typed buffer by type. + * 5. Read from or write to the typed buffer. + * E.g: + * + * try (V8ValueArrayBuffer v8ValueArrayBuffer = v8ValueTypedArray.getArrayBuffer()) { + * LongBuffer longBuffer = v8ValueArrayBuffer.getByteBuffer().order(ByteOrder.nativeOrder()).asLongBuffer(); + * // ... + * } + * + */ +public class V8ValueTypedArray extends V8ValueObject implements IV8ValueTypedArray { + + /** + * The constant NAME_INT_8_ARRAY. + */ + public static final String NAME_INT_8_ARRAY = "Int8Array"; + /** + * The constant NAME_UINT_8_ARRAY. + */ + public static final String NAME_UINT_8_ARRAY = "Uint8Array"; + /** + * The constant NAME_UINT_8_CLAMPED_ARRAY. + */ + public static final String NAME_UINT_8_CLAMPED_ARRAY = "Uint8ClampedArray"; + /** + * The constant NAME_INT_16_ARRAY. + */ + public static final String NAME_INT_16_ARRAY = "Int16Array"; + /** + * The constant NAME_UINT_16_ARRAY. + */ + public static final String NAME_UINT_16_ARRAY = "Uint16Array"; + /** + * The constant NAME_INT_32_ARRAY. + */ + public static final String NAME_INT_32_ARRAY = "Int32Array"; + /** + * The constant NAME_UINT_32_ARRAY. + */ + public static final String NAME_UINT_32_ARRAY = "Uint32Array"; + /** + * The constant NAME_FLOAT_32_ARRAY. + */ + public static final String NAME_FLOAT_32_ARRAY = "Float32Array"; + /** + * The constant NAME_FLOAT_64_ARRAY. + */ + public static final String NAME_FLOAT_64_ARRAY = "Float64Array"; + /** + * The constant NAME_BIG_INT_64_ARRAY. + */ + public static final String NAME_BIG_INT_64_ARRAY = "BigInt64Array"; + /** + * The constant NAME_BIG_UINT_64_ARRAY. + */ + public static final String NAME_BIG_UINT_64_ARRAY = "BigUint64Array"; + + /** + * The constant PROPERTY_BYTE_LENGTH. + */ + public static final String PROPERTY_BYTE_LENGTH = "byteLength"; + /** + * The constant PROPERTY_BUFFER. + */ + public static final String PROPERTY_BUFFER = "buffer"; + /** + * The constant PROPERTY_BYTE_OFFSET. + */ + public static final String PROPERTY_BYTE_OFFSET = "byteOffset"; + /** + * The constant PROPERTY_NAME. + */ + public static final String PROPERTY_NAME = "Name"; + /** + * The constant ONE_BYTE_PER_VALUE. + */ + public static final int ONE_BYTE_PER_VALUE = 1; + /** + * The constant TWO_BYTES_PER_VALUE. + */ + public static final int TWO_BYTES_PER_VALUE = 2; + /** + * The constant FOUR_BYTES_PER_VALUE. + */ + public static final int FOUR_BYTES_PER_VALUE = 4; + /** + * The constant EIGHT_BYTES_PER_VALUE. + */ + public static final int EIGHT_BYTES_PER_VALUE = 8; + /** + * The constant ZERO_BYTE_PER_VALUE. + */ + public static final int ZERO_BYTE_PER_VALUE = 0; + + /** + * The Size in bytes. + */ + protected int sizeInBytes; + /** + * The Type. + */ + protected int type; + + /** + * Instantiates a new V8 value typed array. + * + * @param handle the handle + * @param type the type + */ + V8ValueTypedArray(long handle, int type) { + super(handle); + setType(type); + } + + /** + * Gets JS constructor name by type. + * + * @param type the type + * @return the name + */ + public static String getName(int type) { + switch (type) { + case V8ValueReferenceType.Int8Array: + return NAME_INT_8_ARRAY; + case V8ValueReferenceType.Uint8Array: + return NAME_UINT_8_ARRAY; + case V8ValueReferenceType.Uint8ClampedArray: + return NAME_UINT_8_CLAMPED_ARRAY; + case V8ValueReferenceType.Int16Array: + return NAME_INT_16_ARRAY; + case V8ValueReferenceType.Uint16Array: + return NAME_UINT_16_ARRAY; + case V8ValueReferenceType.Int32Array: + return NAME_INT_32_ARRAY; + case V8ValueReferenceType.Uint32Array: + return NAME_UINT_32_ARRAY; + case V8ValueReferenceType.Float32Array: + return NAME_FLOAT_32_ARRAY; + case V8ValueReferenceType.Float64Array: + return NAME_FLOAT_64_ARRAY; + case V8ValueReferenceType.BigInt64Array: + return NAME_BIG_INT_64_ARRAY; + case V8ValueReferenceType.BigUint64Array: + return NAME_BIG_UINT_64_ARRAY; + default: + return null; + } + } + + @Override + public V8ValueArrayBuffer getBuffer() throws JavetException { + return get(PROPERTY_BUFFER); + } + + @Override + public int getByteLength() throws JavetException { + return getInteger(PROPERTY_BYTE_LENGTH); + } + + @Override + public int getByteOffset() throws JavetException { + return getInteger(PROPERTY_BYTE_OFFSET); + } + + @Override + public int getLength() throws JavetException { + checkV8Runtime(); + return v8Runtime.getLength(this); + } + + @Override + public int getSizeInBytes() { + return sizeInBytes; + } + + @Override + public int getType() { + return type; + } + + /** + * Sets type. + * + * @param type the type + */ + protected void setType(int type) { + switch (type) { + case V8ValueReferenceType.Int8Array: + case V8ValueReferenceType.Uint8Array: + case V8ValueReferenceType.Uint8ClampedArray: + sizeInBytes = ONE_BYTE_PER_VALUE; + break; + case V8ValueReferenceType.Int16Array: + case V8ValueReferenceType.Uint16Array: + sizeInBytes = TWO_BYTES_PER_VALUE; + break; + case V8ValueReferenceType.Int32Array: + case V8ValueReferenceType.Uint32Array: + case V8ValueReferenceType.Float32Array: + sizeInBytes = FOUR_BYTES_PER_VALUE; + break; + case V8ValueReferenceType.Float64Array: + case V8ValueReferenceType.BigInt64Array: + case V8ValueReferenceType.BigUint64Array: + sizeInBytes = EIGHT_BYTES_PER_VALUE; + break; + default: + type = V8ValueReferenceType.Invalid; + sizeInBytes = ZERO_BYTE_PER_VALUE; + break; + } + this.type = type; + } + + @Override + public boolean isValid() { + return type != V8ValueReferenceType.Invalid; + } + + /** + * From byte array. + * + * @param bytes the byte array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromBytes(byte[] bytes) throws JavetException { + if (getType() == V8ValueReferenceType.Int8Array || + getType() == V8ValueReferenceType.Uint8Array || + getType() == V8ValueReferenceType.Uint8ClampedArray) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromBytes(bytes); + } + } + return false; + } + + /** + * From double array. + * + * @param doubles the double array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromDoubles(double[] doubles) throws JavetException { + if (getType() == V8ValueReferenceType.Float64Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromDoubles(doubles); + } + } + return false; + } + + /** + * From float array. + * + * @param floats the float array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromFloats(float[] floats) throws JavetException { + if (getType() == V8ValueReferenceType.Float32Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromFloats(floats); + } + } + return false; + } + + /** + * From integer array. + * + * @param integers the integer array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromIntegers(int[] integers) throws JavetException { + if (getType() == V8ValueReferenceType.Int32Array || + getType() == V8ValueReferenceType.Uint32Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromIntegers(integers); + } + } + return false; + } + + /** + * From long array. + * + * @param longs the long array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromLongs(long[] longs) throws JavetException { + if (getType() == V8ValueReferenceType.BigInt64Array || + getType() == V8ValueReferenceType.BigUint64Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromLongs(longs); + } + } + return false; + } + + /** + * From short array. + * + * @param shorts the short array + * @return the boolean + * @throws JavetException the javet exception + */ + public boolean fromShorts(short[] shorts) throws JavetException { + if (getType() == V8ValueReferenceType.Int16Array || + getType() == V8ValueReferenceType.Uint16Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.fromShorts(shorts); + } + } + return false; + } + + /** + * To byte array. + * + * @return the byte array + * @throws JavetException the javet exception + */ + public byte[] toBytes() throws JavetException { + if (getType() == V8ValueReferenceType.Int8Array || + getType() == V8ValueReferenceType.Uint8Array || + getType() == V8ValueReferenceType.Uint8ClampedArray) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toBytes(); + } + } + return null; + } + + /** + * To float array. + * + * @return the float array + * @throws JavetException the javet exception + */ + public float[] toFloats() throws JavetException { + if (getType() == V8ValueReferenceType.Float32Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toFloats(); + } + } + return null; + } + + /** + * To double array. + * + * @return the double array + * @throws JavetException the javet exception + */ + public double[] toDoubles() throws JavetException { + if (getType() == V8ValueReferenceType.Float64Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toDoubles(); + } + } + return null; + } + + /** + * To int array. + * + * @return the int array + * @throws JavetException the javet exception + */ + public int[] toIntegers() throws JavetException { + if (getType() == V8ValueReferenceType.Int32Array || + getType() == V8ValueReferenceType.Uint32Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toIntegers(); + } + } + return null; + } + + /** + * To long array. + * + * @return the long array + * @throws JavetException the javet exception + */ + public long[] toLongs() throws JavetException { + if (getType() == V8ValueReferenceType.BigInt64Array || + getType() == V8ValueReferenceType.BigUint64Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toLongs(); + } + } + return null; + } + + /** + * To short array. + * + * @return the short array + * @throws JavetException the javet exception + */ + public short[] toShorts() throws JavetException { + if (getType() == V8ValueReferenceType.Int16Array || + getType() == V8ValueReferenceType.Uint16Array) { + try (V8ValueArrayBuffer v8ValueArrayBuffer = getBuffer()) { + return v8ValueArrayBuffer.toShorts(); + } + } + return null; + } +} diff --git a/src/test/java/com/caoccao/javet/BaseTestJavetPool.java b/src/test/java/com/caoccao/javet/BaseTestJavetPool.java new file mode 100644 index 000000000..e129fc47d --- /dev/null +++ b/src/test/java/com/caoccao/javet/BaseTestJavetPool.java @@ -0,0 +1,40 @@ +/* + * 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; + +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interop.engine.IJavetEnginePool; +import com.caoccao.javet.interop.engine.JavetEnginePool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public abstract class BaseTestJavetPool extends BaseTestJavet { + protected IJavetEnginePool javetEnginePool; + + @BeforeEach + public void beforeEach() { + javetEnginePool = new JavetEnginePool(); + javetEnginePool.getConfig().setEngineGuardCheckIntervalMillis(1); + } + + @AfterEach + public void afterEach() throws JavetException { + javetEnginePool.close(); + } + +} diff --git a/src/test/java/com/caoccao/javet/interop/TestV8Native.java b/src/test/java/com/caoccao/javet/interop/TestV8Native.java index 62bf1fbd0..b81258346 100644 --- a/src/test/java/com/caoccao/javet/interop/TestV8Native.java +++ b/src/test/java/com/caoccao/javet/interop/TestV8Native.java @@ -47,7 +47,6 @@ public void testLockAndUnlock() { @Test public void testGetVersion() { String versionString = V8Native.getVersion(); - assertNotNull(versionString); - assertTrue(versionString.startsWith("8.")); + assertEquals(JavetLibLoader.V8_VERSION, versionString); } } diff --git a/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java b/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java index de5a6038f..c35229a20 100644 --- a/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java +++ b/src/test/java/com/caoccao/javet/interop/TestV8Runtime.java @@ -19,12 +19,14 @@ import com.caoccao.javet.BaseTestJavet; import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.exceptions.JavetTerminatedException; import com.caoccao.javet.exceptions.JavetV8RuntimeLockConflictException; import com.caoccao.javet.values.primitive.V8ValueString; -import com.caoccao.javet.values.primitive.V8ValueUndefined; import com.caoccao.javet.values.reference.V8ValueObject; import org.junit.jupiter.api.Test; +import java.util.concurrent.TimeUnit; + import static org.junit.jupiter.api.Assertions.*; public class TestV8Runtime extends BaseTestJavet { @@ -80,9 +82,9 @@ public void testResetContext() throws JavetException { v8Runtime.lock(); assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger()); v8Runtime.getGlobalObject().set("a", new V8ValueString("1")); - v8Runtime.resetContext(); + v8Runtime.unlock().resetContext().lock(); assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger()); - assertTrue(v8Runtime.getGlobalObject().get("a") instanceof V8ValueUndefined); + assertTrue(v8Runtime.getGlobalObject().get("a").isUndefined()); v8Runtime.unlock(); } } @@ -98,8 +100,42 @@ public void testResetIsolate() throws JavetException { v8Runtime.resetIsolate(); v8Runtime.lock(); assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger()); - assertTrue(v8Runtime.getGlobalObject().get("a") instanceof V8ValueUndefined); + assertTrue(v8Runtime.getGlobalObject().get("a").isUndefined()); v8Runtime.unlock(); } } + + @Test + public void testTerminateExecution() throws JavetException { + V8Host v8Host = V8Host.getInstance(); + try (V8Runtime v8Runtime = v8Host.createV8Runtime()) { + // Create a daemon thread monitoring the V8 runtime status. + Thread daemonThread = new Thread(() -> { + // V8 runtime isInUse() does not require lock. + while (!v8Runtime.isInUse()) { + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + // V8 runtime terminateExecution() does not require lock. + v8Runtime.terminateExecution(); + }); + daemonThread.start(); + v8Runtime.lock(); + try { + v8Runtime.getExecutor( + "var count = 0; while (true) { ++count; }") + .executeVoid(); + fail("Failed to throw exception when execution is terminated."); + } catch (JavetTerminatedException e) { + assertFalse(e.isContinuable()); + } + final int count = v8Runtime.getGlobalObject().getInteger("count"); + assertTrue(count > 0, "Count should be greater than 0."); + assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger(), + "V8 runtime should still be able to execute script after being terminated."); + } + } } diff --git a/src/test/java/com/caoccao/javet/interop/engine/TestJavetEngineGuard.java b/src/test/java/com/caoccao/javet/interop/engine/TestJavetEngineGuard.java new file mode 100644 index 000000000..d6a99ca8f --- /dev/null +++ b/src/test/java/com/caoccao/javet/interop/engine/TestJavetEngineGuard.java @@ -0,0 +1,65 @@ +/* + * 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.engine; + +import com.caoccao.javet.BaseTestJavetPool; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.exceptions.JavetTerminatedException; +import com.caoccao.javet.interop.V8Runtime; +import com.caoccao.javet.utils.JavetDateTimeUtils; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestJavetEngineGuard extends BaseTestJavetPool { + @Test + public void testWithoutTermination() throws JavetException { + final long timeoutMillis = 10000; + ZonedDateTime startZonedDateTime = JavetDateTimeUtils.getUTCNow(); + try (IJavetEngine iJavetEngine = javetEnginePool.getEngine()) { + try (IJavetEngineGuard iJavetEngineGuard = iJavetEngine.getGuard(timeoutMillis)) { + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); + assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger()); + } + } + ZonedDateTime endZonedDateTime = JavetDateTimeUtils.getUTCNow(); + Duration duration = Duration.between(startZonedDateTime, endZonedDateTime); + assertTrue(duration.toMillis() < timeoutMillis); + } + + @Test + public void testTermination() throws JavetException { + // Get an engine from the pool as usual. + try (IJavetEngine iJavetEngine = javetEnginePool.getEngine()) { + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); + // Get a guard from the engine and apply try-with-resource pattern. + try (IJavetEngineGuard iJavetEngineGuard = iJavetEngine.getGuard(1)) { + v8Runtime.getExecutor("while (true) {}").executeVoid(); + // That infinite loop will be terminated in 10 seconds by the guard. + } catch (JavetTerminatedException e) { + // JavetTerminatedException will be thrown to mark that. + assertFalse(e.isContinuable()); + } + assertEquals(2, v8Runtime.getExecutor("1 + 1").executeInteger(), + "The V8 runtime is not dead and still be able to execute code afterwards."); + } + } +} diff --git a/src/test/java/com/caoccao/javet/interop/TestPerformance.java b/src/test/java/com/caoccao/javet/interop/engine/TestPerformance.java similarity index 86% rename from src/test/java/com/caoccao/javet/interop/TestPerformance.java rename to src/test/java/com/caoccao/javet/interop/engine/TestPerformance.java index 497d178a4..d54642e96 100644 --- a/src/test/java/com/caoccao/javet/interop/TestPerformance.java +++ b/src/test/java/com/caoccao/javet/interop/engine/TestPerformance.java @@ -15,17 +15,12 @@ * limitations under the License. */ -package com.caoccao.javet.interop; +package com.caoccao.javet.interop.engine; -import com.caoccao.javet.BaseTestJavet; -import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.interop.engine.IJavetEngine; -import com.caoccao.javet.interop.engine.IJavetEnginePool; -import com.caoccao.javet.interop.engine.JavetEnginePool; +import com.caoccao.javet.BaseTestJavetPool; +import com.caoccao.javet.interop.V8Runtime; import com.caoccao.javet.interop.executors.IV8Executor; import com.caoccao.javet.utils.JavetOSUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -40,19 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class TestPerformance extends BaseTestJavet { - protected IJavetEnginePool javetEnginePool; - - @BeforeEach - public void beforeEach() { - javetEnginePool = new JavetEnginePool(); - } - - @AfterEach - public void afterEach() throws JavetException { - javetEnginePool.close(); - } - +public class TestPerformance extends BaseTestJavetPool { @Test @Tag("performance") public void testAdHocContextAnd1Thread() throws Exception { @@ -121,7 +104,7 @@ public void testAdHocContextAnd8Threads() throws Exception { thread.join(); } final long stopTime = System.currentTimeMillis(); - final long tps = iterations * (long) threadCount * 1000 / (stopTime-startTime); + final long tps = iterations * (long) threadCount * 1000 / (stopTime - startTime); logger.logInfo("Ad-hoc Context with 8 Threads: {0}", tps); updateDoc("Ad-hoc Context with 8 Threads", tps); } @@ -154,7 +137,7 @@ public void testSingleContextAnd8Threads() throws Exception { thread.join(); } final long stopTime = System.currentTimeMillis(); - final long tps = iterations * (long) threadCount * 1000 / (stopTime-startTime); + final long tps = iterations * (long) threadCount * 1000 / (stopTime - startTime); logger.logInfo("Single Context with 8 Threads: {0}", tps); updateDoc("Single Context with 8 Threads", tps); } @@ -163,7 +146,7 @@ protected void updateDoc(String prefix, long tps) throws IOException { DecimalFormat decimalFormat = new DecimalFormat("#,###"); File docFile = new File( JavetOSUtils.WORKING_DIRECTORY, - "docs/performance.rst"); + "docs/development/performance.rst"); List lines = new ArrayList<>(); for (String line : Files.readAllLines(docFile.toPath(), StandardCharsets.UTF_8)) { if (line.startsWith(prefix)) { diff --git a/src/test/java/com/caoccao/javet/tutorial/DecimalJavet.java b/src/test/java/com/caoccao/javet/tutorial/DecimalJavet.java index 22798adeb..ab21c73d9 100644 --- a/src/test/java/com/caoccao/javet/tutorial/DecimalJavet.java +++ b/src/test/java/com/caoccao/javet/tutorial/DecimalJavet.java @@ -20,20 +20,25 @@ import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.interfaces.IJavetClosable; import com.caoccao.javet.interfaces.IJavetLogger; -import com.caoccao.javet.interop.V8Host; import com.caoccao.javet.interop.V8Runtime; -import com.caoccao.javet.utils.JavetDefaultLogger; +import com.caoccao.javet.interop.engine.IJavetEngine; +import com.caoccao.javet.interop.engine.IJavetEnginePool; +import com.caoccao.javet.interop.engine.JavetEnginePool; import com.caoccao.javet.utils.JavetOSUtils; +import com.caoccao.javet.values.primitive.V8ValueString; +import com.caoccao.javet.values.reference.V8ValueFunction; +import com.caoccao.javet.values.reference.V8ValueObject; import java.io.File; +import java.math.BigDecimal; public class DecimalJavet implements IJavetClosable { - private IJavetLogger logger; - private V8Runtime v8Runtime; + private IJavetEnginePool iJavetEnginePool; + private IJavetEngine iJavetEngine; public DecimalJavet() { - logger = new JavetDefaultLogger(getClass().getName()); - v8Runtime = null; + iJavetEnginePool = new JavetEnginePool(); + iJavetEngine = iJavetEnginePool.getEngine(); } public static void main(String[] args) throws JavetException { @@ -53,31 +58,49 @@ public void loadJS() throws JavetException { JavetOSUtils.WORKING_DIRECTORY, "scripts/node/node_modules/decimal.js/decimal.js"); if (decimalJSFile.exists() && decimalJSFile.canRead()) { - logger.logInfo("Loading {0}.", decimalJSFile.getAbsolutePath()); - v8Runtime = V8Host.getInstance().createV8Runtime(); - v8Runtime.lock(); + getLogger().logInfo("Loading {0}.", decimalJSFile.getAbsolutePath()); + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); v8Runtime.getExecutor(decimalJSFile).executeVoid(); } else { - logger.logError("{0} is not found.", decimalJSFile.getAbsolutePath()); - logger.logError("Please make sure NodeJS is installed, then visit script/node directory and run npm install."); + getLogger().logError("{0} is not found.", decimalJSFile.getAbsolutePath()); + getLogger().logError("Please make sure NodeJS is installed, then visit script/node directory and run npm install."); } } public void test() throws JavetException { - logger.logInfo("1.23 + 2.34 = {0}", v8Runtime.getExecutor( + V8Runtime v8Runtime = iJavetEngine.getV8Runtime(); + getLogger().logInfo("1.23 + 2.34 = {0}", v8Runtime.getExecutor( "const a = new Decimal(1.23);" + "const b = new Decimal(2.34);" + "a.add(b).toString();").executeString()); + try (V8ValueFunction v8ValueFunctionDecimal = v8Runtime.getGlobalObject().get("Decimal")) { + try (V8ValueObject v8ValueObjectDecimal = v8ValueFunctionDecimal.call( + null, new V8ValueString("123.45"))) { + getLogger().logInfo(v8ValueObjectDecimal.toString()); + if (v8ValueObjectDecimal.has("constructor")) { + try (V8ValueFunction v8ValueFunction = v8ValueObjectDecimal.get("constructor")) { + String name = v8ValueFunction.getString("name"); + if ("Decimal".equals(name)) { + BigDecimal bigDecimal = new BigDecimal(v8ValueObjectDecimal.toString()); + getLogger().logInfo("BigDecimal: {0}", bigDecimal.toString()); + } + } + } + } + } } public IJavetLogger getLogger() { - return logger; + return iJavetEnginePool.getConfig().getJavetLogger(); } @Override public void close() throws JavetException { - if (v8Runtime != null) { - v8Runtime.close(); + if (iJavetEngine != null) { + iJavetEngine.close(); + } + if (iJavetEnginePool != null) { + iJavetEnginePool.close(); } } } diff --git a/src/test/java/com/caoccao/javet/utils/converters/TestJavetObjectConverter.java b/src/test/java/com/caoccao/javet/utils/converters/TestJavetObjectConverter.java new file mode 100644 index 000000000..ee37406d5 --- /dev/null +++ b/src/test/java/com/caoccao/javet/utils/converters/TestJavetObjectConverter.java @@ -0,0 +1,247 @@ +/* + * 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.utils.converters; + +import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.entities.JavetEntityMap; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.utils.JavetDateTimeUtils; +import com.caoccao.javet.values.V8ValueReferenceType; +import com.caoccao.javet.values.primitive.V8ValueInteger; +import com.caoccao.javet.values.primitive.V8ValueString; +import com.caoccao.javet.values.reference.*; +import org.junit.jupiter.api.Test; + +import java.time.ZonedDateTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings("unchecked") +public class TestJavetObjectConverter extends BaseTestJavetRuntime { + protected JavetObjectConverter converter; + + public TestJavetObjectConverter() { + converter = new JavetObjectConverter(); + } + + @Test + public void testArray() throws JavetException { + try (V8ValueArray v8ValueArray = v8Runtime.createV8ValueArray()) { + v8ValueArray.push(new V8ValueString("abc")); + v8ValueArray.push(new V8ValueInteger(123)); + List list = (List) converter.toObject(v8ValueArray); + assertEquals(2, list.size()); + assertEquals("abc", list.get(0)); + assertEquals(123, list.get(1)); + } + // ArrayList + try (V8ValueArray v8ValueArray = converter.toV8Value( + v8Runtime, Arrays.asList("abc", 123))) { + assertEquals(2, v8ValueArray.getLength()); + assertEquals("abc", v8ValueArray.getString(0)); + assertEquals(123, v8ValueArray.getInteger(1)); + } + // boolean[] + try (V8ValueArray v8ValueArray = converter.toV8Value( + v8Runtime, new boolean[]{true, false})) { + assertEquals(2, v8ValueArray.getLength()); + assertEquals(true, v8ValueArray.getBoolean(0)); + assertEquals(false, v8ValueArray.getBoolean(1)); + } + // String[] + try (V8ValueArray v8ValueArray = converter.toV8Value( + v8Runtime, new String[]{"abc", "def"})) { + assertEquals(2, v8ValueArray.getLength()); + assertEquals("abc", v8ValueArray.getString(0)); + assertEquals("def", v8ValueArray.getString(1)); + } + // ZonedDateTime[] + try (V8ValueArray v8ValueArray = converter.toV8Value( + v8Runtime, new ZonedDateTime[]{ + JavetDateTimeUtils.toZonedDateTime(123L), + JavetDateTimeUtils.toZonedDateTime(456L)})) { + assertEquals(2, v8ValueArray.getLength()); + assertEquals(123L, v8ValueArray.getZonedDateTime(0).toInstant().toEpochMilli()); + assertEquals(456L, v8ValueArray.getZonedDateTime(1).toInstant().toEpochMilli()); + } + // Object[] + try (V8ValueArray v8ValueArray = converter.toV8Value( + v8Runtime, new Object[]{1, "abc"})) { + assertEquals(2, v8ValueArray.getLength()); + assertEquals(1, v8ValueArray.getInteger(0)); + assertEquals("abc", v8ValueArray.getString(1)); + } + } + + @Test + public void testMap() throws JavetException { + try (V8ValueMap v8ValueMap = v8Runtime.createV8ValueMap()) { + v8ValueMap.set("x", new V8ValueString("abc")); + assertEquals("abc", v8ValueMap.getString("x")); + JavetEntityMap map = (JavetEntityMap) converter.toObject(v8ValueMap); + assertEquals(1, map.size()); + assertEquals("abc", map.get("x")); + } + try (V8ValueMap v8ValueMap = converter.toV8Value( + v8Runtime, new JavetEntityMap() {{ + put("x", "abc"); + }})) { + assertEquals(1, v8ValueMap.getSize()); + assertEquals("abc", v8ValueMap.getString("x")); + } + } + + @Test + public void testObject() throws JavetException { + try (V8ValueObject v8ValueObject = v8Runtime.createV8ValueObject()) { + v8ValueObject.set("x", new V8ValueString("abc")); + assertEquals("abc", v8ValueObject.getString("x")); + Map map = (Map) converter.toObject(v8ValueObject); + assertTrue(map instanceof HashMap); + assertEquals(1, map.size()); + assertEquals("abc", map.get("x")); + } + try (V8ValueObject v8ValueObject = converter.toV8Value( + v8Runtime, new HashMap() {{ + put("x", "abc"); + }})) { + assertEquals("abc", v8ValueObject.getString("x")); + } + } + + @Test + public void testSet() throws JavetException { + try (V8ValueSet v8ValueSet = v8Runtime.createV8ValueSet()) { + v8ValueSet.add(new V8ValueString("abc")); + assertTrue(v8ValueSet.has("abc")); + Set set = (Set) converter.toObject(v8ValueSet); + assertEquals(1, set.size()); + assertTrue(set.contains("abc")); + } + try (V8ValueSet v8ValueSet = converter.toV8Value( + v8Runtime, new HashSet(Arrays.asList("a", "b", "c")))) { + assertEquals(3, v8ValueSet.getSize()); + assertTrue(v8ValueSet.has("a")); + assertTrue(v8ValueSet.has("b")); + assertTrue(v8ValueSet.has("c")); + } + } + + @Test + public void testTypedArrayByteArray() throws JavetException { + byte[] bytes = new byte[]{(byte) 0x01, (byte) 0x02, (byte) 0x03}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, bytes)) { + assertEquals(bytes.length, v8ValueTypedArray.getLength()); + assertEquals(bytes.length, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(bytes, v8ValueTypedArray.toBytes()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.Int8Array, bytes.length)) { + assertTrue(v8ValueTypedArray.fromBytes(bytes)); + byte[] newBytes = (byte[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(bytes, newBytes); + } + } + + @Test + public void testTypedArrayDoubleArray() throws JavetException { + double[] doubles = new double[]{1.23D, 2.34D, 3.45D}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, doubles)) { + assertEquals(doubles.length, v8ValueTypedArray.getLength()); + assertEquals(doubles.length * v8ValueTypedArray.getSizeInBytes(), v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(doubles, v8ValueTypedArray.toDoubles()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.Float64Array, doubles.length)) { + assertTrue(v8ValueTypedArray.fromDoubles(doubles)); + double[] newDoubles = (double[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(doubles, newDoubles, 0.001D); + } + } + + @Test + public void testTypedArrayFloatArray() throws JavetException { + float[] floats = new float[]{1.23F, 2.34F, 3.45F}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, floats)) { + assertEquals(floats.length, v8ValueTypedArray.getLength()); + assertEquals(floats.length * v8ValueTypedArray.getSizeInBytes(), v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(floats, v8ValueTypedArray.toFloats()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.Float32Array, floats.length)) { + assertTrue(v8ValueTypedArray.fromFloats(floats)); + float[] newFloats = (float[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(floats, newFloats, 0.001F); + } + } + + @Test + public void testTypedArrayLongArray() throws JavetException { + long[] longs = new long[]{1L, 2L, 3L}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, longs)) { + assertEquals(longs.length, v8ValueTypedArray.getLength()); + assertEquals(longs.length * v8ValueTypedArray.getSizeInBytes(), v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(longs, v8ValueTypedArray.toLongs()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.BigInt64Array, longs.length)) { + assertTrue(v8ValueTypedArray.fromLongs(longs)); + long[] newLongs = (long[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(longs, newLongs); + } + } + + @Test + public void testTypedArrayIntegerArray() throws JavetException { + int[] integers = new int[]{1, 2, 3}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, integers)) { + assertEquals(integers.length, v8ValueTypedArray.getLength()); + assertEquals(integers.length * v8ValueTypedArray.getSizeInBytes(), v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(integers, v8ValueTypedArray.toIntegers()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.Int32Array, integers.length)) { + assertTrue(v8ValueTypedArray.fromIntegers(integers)); + int[] newIntegers = (int[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(integers, newIntegers); + } + } + + @Test + public void testTypedArrayShortArray() throws JavetException { + short[] shorts = new short[]{(short) 0x01, (short) 0x02, (short) 0x03}; + try (V8ValueTypedArray v8ValueTypedArray = converter.toV8Value(v8Runtime, shorts)) { + assertEquals(shorts.length, v8ValueTypedArray.getLength()); + assertEquals(shorts.length * v8ValueTypedArray.getSizeInBytes(), v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertArrayEquals(shorts, v8ValueTypedArray.toShorts()); + } + try (V8ValueTypedArray v8ValueTypedArray = + v8Runtime.createV8ValueTypedArray(V8ValueReferenceType.Int16Array, shorts.length)) { + assertTrue(v8ValueTypedArray.fromShorts(shorts)); + short[] newShorts = (short[]) converter.toObject(v8ValueTypedArray); + assertArrayEquals(shorts, newShorts); + } + } +} diff --git a/src/test/java/com/caoccao/javet/utils/converters/TestJavetPrimitiveConverter.java b/src/test/java/com/caoccao/javet/utils/converters/TestJavetPrimitiveConverter.java new file mode 100644 index 000000000..0dd227c88 --- /dev/null +++ b/src/test/java/com/caoccao/javet/utils/converters/TestJavetPrimitiveConverter.java @@ -0,0 +1,86 @@ +/* + * 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.utils.converters; + +import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.utils.JavetDateTimeUtils; +import com.caoccao.javet.values.primitive.*; +import org.junit.jupiter.api.Test; + +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestJavetPrimitiveConverter extends BaseTestJavetRuntime { + protected JavetPrimitiveConverter converter; + + public TestJavetPrimitiveConverter() { + converter = new JavetPrimitiveConverter(); + } + + @Test + public void testBoolean() throws JavetException { + assertTrue((boolean) converter.toObject(new V8ValueBoolean(true))); + assertTrue(((V8ValueBoolean) converter.toV8Value(v8Runtime, true)).getValue()); + assertFalse((boolean) converter.toObject(new V8ValueBoolean(false))); + assertFalse(((V8ValueBoolean) converter.toV8Value(v8Runtime, false)).getValue()); + } + + @Test + public void testDouble() throws JavetException { + assertEquals(1.23D, (double) converter.toObject(new V8ValueDouble(1.23D)), 0.001); + assertEquals(1.23D, ((V8ValueDouble) converter.toV8Value(v8Runtime, Double.valueOf(1.23D))).getValue(), 0.001); + } + + @Test + public void testFloat() throws JavetException { + assertEquals(1.23F, ((Double) converter.toObject(new V8ValueDouble(1.23F))).floatValue(), 0.001); + assertEquals(1.23F, ((V8ValueDouble) converter.toV8Value(v8Runtime, Float.valueOf(1.23F))).getValue(), 0.001); + } + + @Test + public void testLong() throws JavetException { + assertEquals(123L, (long) converter.toObject(new V8ValueLong(123L))); + assertEquals(123L, ((V8ValueLong) converter.toV8Value(v8Runtime, Long.valueOf(123L))).getValue()); + } + + @Test + public void testNull() throws JavetException { + assertNull(converter.toObject(null)); + assertNull(converter.toObject(v8Runtime.createV8ValueNull())); + assertTrue(converter.toV8Value(v8Runtime, null).isNull()); + } + + @Test + public void testString() throws JavetException { + assertEquals("abc", (String) converter.toObject(new V8ValueString("abc"))); + assertEquals("abc", ((V8ValueString) converter.toV8Value(v8Runtime, "abc")).getValue()); + } + + @Test + public void testUndefined() throws JavetException { + assertNull(converter.toObject(v8Runtime.createV8ValueUndefined())); + } + + @Test + public void testZonedDateTime() throws JavetException { + assertEquals(123L, ((ZonedDateTime) converter.toObject(new V8ValueZonedDateTime(123L))).toInstant().toEpochMilli()); + assertEquals(123L, ((V8ValueZonedDateTime) converter.toV8Value(v8Runtime, JavetDateTimeUtils.toZonedDateTime(123L))).getValue().toInstant().toEpochMilli()); + } +} diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueBoolean.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueBoolean.java index fc22da7c3..d31c10e2f 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueBoolean.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueBoolean.java @@ -49,4 +49,13 @@ public void testBooleanObject() throws JavetException { assertTrue(v8Runtime.getExecutor("Boolean(true)").executeBoolean()); assertFalse(v8Runtime.getExecutor("Boolean(false)").executeBoolean()); } + + @Test + public void testEquals() throws JavetException { + V8ValueBoolean v8ValueBoolean = v8Runtime.getExecutor("true").execute(); + assertTrue(v8ValueBoolean.equals(new V8ValueBoolean(true))); + assertFalse(v8ValueBoolean.equals(null)); + assertFalse(v8ValueBoolean.equals(new V8ValueBoolean(false))); + assertFalse(v8ValueBoolean.equals(v8Runtime.createV8ValueUndefined())); + } } diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueDouble.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueDouble.java index 525e732f1..3d9fdd9f2 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueDouble.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueDouble.java @@ -21,12 +21,34 @@ import com.caoccao.javet.exceptions.JavetException; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; public class TestV8ValueDouble extends BaseTestJavetRuntime { public static final double DELTA = 0.001; + @Test + public void testEquals() throws JavetException { + V8ValueDouble v8ValueDouble = v8Runtime.getExecutor("1.23").execute(); + assertTrue(v8ValueDouble.equals(new V8ValueDouble(1.23D))); + assertFalse(v8ValueDouble.equals(null)); + assertFalse(v8ValueDouble.equals(new V8ValueDouble(1.24D))); + assertFalse(v8ValueDouble.equals(v8Runtime.createV8ValueUndefined())); + } + + @Test + public void testNaNAndInfiniteAndFinite() throws JavetException { + V8ValueDouble v8ValueDouble = v8Runtime.getExecutor("NaN").execute(); + assertNotNull(v8ValueDouble); + assertEquals(v8Runtime, v8ValueDouble.getV8Runtime()); + assertEquals(Double.NaN, v8ValueDouble.getValue()); + assertTrue(v8ValueDouble.isNaN()); + v8ValueDouble = v8Runtime.getExecutor("1/0").execute(); + assertTrue(v8ValueDouble.isInfinite()); + v8ValueDouble = v8Runtime.getExecutor("1/2").execute(); + assertTrue(v8ValueDouble.isFinite()); + } + @Test public void testNumber() throws JavetException { try (V8ValueDouble v8ValueDouble = v8Runtime.getExecutor("1.23").execute()) { diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueInteger.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueInteger.java index 801c9df82..a2e1ffe7a 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueInteger.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueInteger.java @@ -21,10 +21,19 @@ import com.caoccao.javet.exceptions.JavetException; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; public class TestV8ValueInteger extends BaseTestJavetRuntime { + @Test + public void testEquals() throws JavetException { + V8ValueInteger v8ValueInteger = v8Runtime.getExecutor("1").execute(); + assertTrue(v8ValueInteger.equals(new V8ValueInteger(1))); + assertFalse(v8ValueInteger.equals(null)); + assertFalse(v8ValueInteger.equals(new V8ValueInteger(2))); + assertFalse(v8ValueInteger.equals(new V8ValueLong(1))); + } + @Test public void testInt32() throws JavetException { try (V8ValueInteger v8ValueInteger = v8Runtime.getExecutor("1 + 1").execute()) { diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueLong.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueLong.java index eb1e522c0..369f8a71a 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueLong.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueLong.java @@ -17,12 +17,12 @@ package com.caoccao.javet.values.primitive; -import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.reference.V8ValueObject; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; public class TestV8ValueLong extends BaseTestJavetRuntime { @Test @@ -34,6 +34,14 @@ public void testBigInt() throws JavetException { assertEquals(-2L, v8Runtime.getExecutor("-2n").executeLong()); assertEquals(v8Runtime, v8ValueLong.getV8Runtime()); } + try (V8ValueObject v8ValueObject = v8Runtime.createV8ValueObject()) { + v8ValueObject.set("a", new V8ValueLong(123L)); + assertEquals(123L, v8ValueObject.getLong("a")); + v8Runtime.getGlobalObject().set("x", v8ValueObject); + } + try (V8ValueObject v8ValueObject = v8Runtime.getGlobalObject().get("x")) { + assertEquals(123L, v8ValueObject.getLong("a")); + } } @Test @@ -41,6 +49,15 @@ public void testBigIntObject() throws JavetException { assertEquals(123L, v8Runtime.getExecutor("BigInt(123n)").executeLong()); } + @Test + public void testEquals() throws JavetException { + V8ValueLong v8ValueLong = v8Runtime.getExecutor("1n").execute(); + assertTrue(v8ValueLong.equals(new V8ValueLong(1L))); + assertFalse(v8ValueLong.equals(null)); + assertFalse(v8ValueLong.equals(new V8ValueLong(2L))); + assertFalse(v8ValueLong.equals(new V8ValueInteger(1))); + } + @Test public void testString() throws JavetException { assertEquals("4611686018427387904", v8Runtime.getExecutor("(2n ** 62n).toString()").executeString()); diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueNull.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueNull.java index 1191dc479..d09b16e56 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueNull.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueNull.java @@ -19,13 +19,19 @@ import com.caoccao.javet.BaseTestJavetRuntime; import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.values.primitive.V8ValueNull; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; public class TestV8ValueNull extends BaseTestJavetRuntime { + @Test + public void testEquals() throws JavetException { + V8ValueNull v8ValueNull = v8Runtime.getExecutor("null").execute(); + assertTrue(v8ValueNull.equals(v8Runtime.createV8ValueNull())); + assertFalse(v8ValueNull.equals(null)); + assertFalse(v8ValueNull.equals(v8Runtime.createV8ValueUndefined())); + } + @Test public void testNull() throws JavetException { V8ValueNull v8ValueNull = v8Runtime.getExecutor("null").execute(); diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueString.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueString.java index e210d004d..fca466acb 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueString.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueString.java @@ -21,10 +21,19 @@ import com.caoccao.javet.exceptions.JavetException; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; public class TestV8ValueString extends BaseTestJavetRuntime { + @Test + public void testEquals() throws JavetException { + V8ValueString v8ValueString = v8Runtime.getExecutor("'abc'").execute(); + assertTrue(v8ValueString.equals(new V8ValueString("abc"))); + assertFalse(v8ValueString.equals(null)); + assertFalse(v8ValueString.equals(new V8ValueString("def"))); + assertFalse(v8ValueString.equals(new V8ValueLong(1))); + } + @Test public void testString() throws JavetException { try (V8ValueString v8ValueString = v8Runtime.getExecutor("'abc' + 'def'").execute()) { diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueUndefined.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueUndefined.java index fb444f1c7..1c56609f4 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueUndefined.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueUndefined.java @@ -19,13 +19,19 @@ import com.caoccao.javet.BaseTestJavetRuntime; import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.values.primitive.V8ValueUndefined; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; public class TestV8ValueUndefined extends BaseTestJavetRuntime { + @Test + public void testEquals() throws JavetException { + V8ValueUndefined v8ValueUndefined = v8Runtime.getExecutor("undefined").execute(); + assertTrue(v8ValueUndefined.equals(v8Runtime.createV8ValueUndefined())); + assertFalse(v8ValueUndefined.equals(null)); + assertFalse(v8ValueUndefined.equals(v8Runtime.createV8ValueNull())); + } + @Test public void testUndefined() throws JavetException { try (V8ValueUndefined v8ValueUndefined = v8Runtime.getExecutor("undefined").execute()) { diff --git a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueZonedDateTime.java b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueZonedDateTime.java index c63f858ea..b3fabe775 100644 --- a/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueZonedDateTime.java +++ b/src/test/java/com/caoccao/javet/values/primitive/TestV8ValueZonedDateTime.java @@ -19,6 +19,7 @@ import com.caoccao.javet.BaseTestJavetRuntime; import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.utils.JavetDateTimeUtils; import org.junit.jupiter.api.Test; import java.time.ZoneId; @@ -30,6 +31,17 @@ public class TestV8ValueZonedDateTime extends BaseTestJavetRuntime { public static final int DELTA = 2000; + @Test + public void testEquals() throws JavetException { + V8ValueZonedDateTime v8ValueZonedDateTime = v8Runtime.getExecutor("new Date(123)").execute(); + assertTrue(v8ValueZonedDateTime.equals( + new V8ValueZonedDateTime(JavetDateTimeUtils.toZonedDateTime(123L)))); + assertFalse(v8ValueZonedDateTime.equals(null)); + assertFalse(v8ValueZonedDateTime.equals( + new V8ValueZonedDateTime(JavetDateTimeUtils.toZonedDateTime(234L)))); + assertFalse(v8ValueZonedDateTime.equals(new V8ValueLong(1))); + } + @Test public void testZonedDateTime() throws JavetException { ZonedDateTime now = ZonedDateTime.now(); diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArray.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArray.java index beb854ae7..9a1e97c5b 100644 --- a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArray.java +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArray.java @@ -20,10 +20,10 @@ import com.caoccao.javet.BaseTestJavetRuntime; import com.caoccao.javet.exceptions.JavetException; import com.caoccao.javet.values.primitive.*; -import com.caoccao.javet.values.virtual.V8VirtualList; import org.junit.jupiter.api.Test; import java.time.ZoneId; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -46,12 +46,11 @@ public void testGetAndSet() throws JavetException { assertEquals("x,y,z", v8ValueArray.toString()); assertEquals("[object Array]", v8ValueArray.toProtoString()); assertEquals("[\"x\",\"y\",\"z\"]", v8ValueArray.toJsonString()); - try (V8VirtualList keys = v8ValueArray.getKeys()) { - assertEquals(3, keys.size()); - assertEquals(0, keys.get(0)); - assertEquals(1, keys.get(1)); - assertEquals(2, keys.get(2)); - } + List keys = v8ValueArray.getKeys(); + assertEquals(3, keys.size()); + assertEquals(0, keys.get(0)); + assertEquals(1, keys.get(1)); + assertEquals(2, keys.get(2)); } } @@ -72,8 +71,8 @@ public void testGet() throws JavetException { assertTrue(v8ValueArray.getBoolean(3)); assertEquals(1.23, ((V8ValueDouble) v8ValueArray.get(4)).getValue(), 0.001); assertEquals(1.23, v8ValueArray.getDouble(4), 0.001); - assertTrue(v8ValueArray.get(-1) instanceof V8ValueUndefined); - assertTrue(v8ValueArray.get(100) instanceof V8ValueUndefined); + assertTrue(v8ValueArray.get(-1).isUndefined()); + assertTrue(v8ValueArray.get(100).isUndefined()); assertEquals(1, v8Runtime.getReferenceCount()); try (V8ValueArray childV8ValueArray = v8ValueArray.get(5)) { assertNotNull(childV8ValueArray); @@ -81,7 +80,7 @@ public void testGet() throws JavetException { assertEquals(4, childV8ValueArray.getLength()); assertEquals(4, childV8ValueArray.getInteger(0)); assertEquals(5, childV8ValueArray.getInteger(1)); - assertTrue(childV8ValueArray.get(2) instanceof V8ValueNull); + assertTrue(childV8ValueArray.get(2).isNull()); assertEquals( "2021-01-27T01:17:03.719Z[UTC]", childV8ValueArray.getZonedDateTime(3).withZoneSameInstant(ZoneId.of("UTC")).toString()); diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArrayBuffer.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArrayBuffer.java new file mode 100644 index 000000000..203db2bd3 --- /dev/null +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueArrayBuffer.java @@ -0,0 +1,63 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.exceptions.JavetException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestV8ValueArrayBuffer extends BaseTestJavetRuntime { + + @Test + public void testFromV8() throws JavetException { + final int byteLength = 16; + try (V8ValueArrayBuffer v8ValueArrayBuffer = + v8Runtime.getExecutor("const a = new ArrayBuffer(" + byteLength + "); a;").execute()) { + assertEquals("{}", v8ValueArrayBuffer.toJsonString()); + assertEquals("[object ArrayBuffer]", v8ValueArrayBuffer.toString()); + assertEquals(byteLength, v8ValueArrayBuffer.getByteLength()); + for (int i = 0; i < byteLength; i++) { + v8ValueArrayBuffer.getByteBuffer().put(i, (byte) i); + } + v8Runtime.getExecutor("const b = new Int8Array(a);").executeVoid(); + for (int i = 0; i < byteLength; i++) { + assertEquals(i, v8Runtime.getExecutor("b[" + i + "];").executeInteger()); + } + } + } + + @Test + public void testToV8() throws JavetException { + final int byteLength = 16; + try (V8ValueArrayBuffer v8ValueArrayBuffer = v8Runtime.createV8ValueArrayBuffer(byteLength)) { + assertEquals("{}", v8ValueArrayBuffer.toJsonString()); + assertEquals("[object ArrayBuffer]", v8ValueArrayBuffer.toString()); + assertEquals(byteLength, v8ValueArrayBuffer.getByteLength()); + for (int i = 0; i < byteLength; i++) { + v8ValueArrayBuffer.getByteBuffer().put(i, (byte) i); + } + v8Runtime.getGlobalObject().set("a", v8ValueArrayBuffer); + v8Runtime.getExecutor("const b = new Int8Array(a);").executeVoid(); + for (int i = 0; i < byteLength; i++) { + assertEquals(i, v8Runtime.getExecutor("b[" + i + "];").executeInteger()); + } + } + } +} diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueDataView.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueDataView.java new file mode 100644 index 000000000..a9dffea3b --- /dev/null +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueDataView.java @@ -0,0 +1,70 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.exceptions.JavetException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestV8ValueDataView extends BaseTestJavetRuntime { + + @Test + public void testFromV8() throws JavetException { + final int byteLength = 16; + try (V8ValueArrayBuffer v8ValueArrayBuffer = v8Runtime.getExecutor( + "const a = new ArrayBuffer(" + byteLength + "); a;").execute()) { + assertEquals("{}", v8ValueArrayBuffer.toJsonString()); + assertEquals("[object ArrayBuffer]", v8ValueArrayBuffer.toString()); + assertEquals(byteLength, v8ValueArrayBuffer.getByteLength()); + for (int i = 0; i < byteLength; i++) { + v8ValueArrayBuffer.getByteBuffer().put(i, (byte) i); + } + } + try (V8ValueDataView v8ValueDataView = v8Runtime.getExecutor( + "const b = new DataView(a); b;").execute()) { + assertEquals("{}", v8ValueDataView.toJsonString()); + assertEquals("[object DataView]", v8ValueDataView.toString()); + assertEquals(byteLength, v8ValueDataView.getByteLength()); + for (int i = 0; i < byteLength; i++) { + assertEquals(i, v8ValueDataView.getInt8(i)); + } + } + } + + @Test + public void testToV8() throws JavetException { + final int byteLength = 16; + try (V8ValueArrayBuffer v8ValueArrayBuffer = v8Runtime.createV8ValueArrayBuffer(byteLength)) { + try (V8ValueDataView v8ValueDataView = v8Runtime.createV8ValueDataView(v8ValueArrayBuffer)) { + assertEquals("{}", v8ValueDataView.toJsonString()); + assertEquals("[object DataView]", v8ValueDataView.toString()); + assertEquals(byteLength, v8ValueDataView.getByteLength()); + for (int i = 0; i < byteLength; i++) { + v8ValueDataView.setInt8(i, (byte) i); + } + } + byte[] bytes = new byte[byteLength]; + v8ValueArrayBuffer.getByteBuffer().get(bytes); + for (int i = 0; i < byteLength; i++) { + assertEquals(i, bytes[i]); + } + } + } +} diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueFunction.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueFunction.java index 3c8c23926..5a3246449 100644 --- a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueFunction.java +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueFunction.java @@ -26,7 +26,6 @@ import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.primitive.V8ValueInteger; import com.caoccao.javet.values.primitive.V8ValueString; -import com.caoccao.javet.values.primitive.V8ValueUndefined; import org.junit.jupiter.api.Test; import java.time.ZonedDateTime; @@ -34,6 +33,25 @@ import static org.junit.jupiter.api.Assertions.*; public class TestV8ValueFunction extends BaseTestJavetRuntime { + @Test + public void testAnonymousFunction() throws JavetException { + String codeString = "() => '123測試'"; + try (V8Value v8Value = v8Runtime.getExecutor(codeString).execute()) { + assertNotNull(v8Value); + assertTrue(v8Value instanceof V8ValueFunction); + V8ValueFunction v8ValueFunction = (V8ValueFunction) v8Value; + assertEquals(codeString, v8ValueFunction.toString()); + assertEquals("123測試", v8ValueFunction.callString(null)); + } + v8Runtime.getGlobalObject().setFunction("a", codeString); + assertEquals("123測試", v8Runtime.getExecutor("a();").executeString()); + v8Runtime.getGlobalObject().setFunction("b", "(x) => x + 1;"); + assertEquals(2, v8Runtime.getExecutor("b(1);").executeInteger()); + v8Runtime.getGlobalObject().delete("a"); + v8Runtime.getGlobalObject().delete("b"); + v8Runtime.requestGarbageCollectionForTesting(true); + } + @Test public void testArrayPush() throws JavetException { try (V8ValueArray v8ValueArray = v8Runtime.getExecutor("const a = []; a;").execute()) { @@ -204,7 +222,7 @@ public void testCallbackEchoVIVOWithoutThis() throws JavetException, NoSuchMetho globalObject.delete("echo"); } assertTrue(mockCallbackReceiver.isCalled()); - assertTrue(globalObject.get("a") instanceof V8ValueUndefined); + assertTrue(globalObject.get("a").isUndefined()); } @Test diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueMap.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueMap.java index 31d8f190e..ed7096a4a 100644 --- a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueMap.java +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueMap.java @@ -19,16 +19,13 @@ import com.caoccao.javet.BaseTestJavetRuntime; import com.caoccao.javet.exceptions.JavetException; -import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.primitive.V8ValueInteger; import com.caoccao.javet.values.primitive.V8ValueString; -import com.caoccao.javet.values.virtual.V8VirtualList; import org.junit.jupiter.api.Test; -import java.util.List; - import static org.junit.jupiter.api.Assertions.*; +@SuppressWarnings("unchecked") public class TestV8ValueMap extends BaseTestJavetRuntime { @Test public void testGetAndHas() throws JavetException { @@ -50,28 +47,29 @@ public void testGetAndHas() throws JavetException { assertNotNull(iV8ValueArray); assertEquals(0, iV8ValueArray.getLength()); } - List keys = v8ValueMap.getKeys(); - assertEquals(3, keys.size()); - assertEquals("x", ((V8ValueString) keys.get(0)).getValue()); - assertEquals("y", ((V8ValueString) keys.get(1)).getValue()); - assertEquals(3, ((V8ValueInteger) keys.get(2)).getValue()); - List values = v8ValueMap.getValues(); - assertEquals(3, values.size()); - assertEquals(1, ((V8ValueInteger) values.get(0)).getValue()); - assertEquals("b", ((V8ValueString) values.get(1)).getValue()); - assertEquals("c", ((V8ValueString) values.get(2)).getValue()); - try (V8VirtualList entries = v8ValueMap.getEntries()) { - assertEquals(3, values.size()); - assertEquals(4, v8Runtime.getReferenceCount()); - V8ValueArray entry = (V8ValueArray) entries.get(0); - assertEquals("x", entry.getString(0)); - assertEquals(1, entry.getInteger(1)); - entry = (V8ValueArray) entries.get(1); - assertEquals("y", entry.getString(0)); - assertEquals("b", entry.getString(1)); - entry = (V8ValueArray) entries.get(2); - assertEquals(3, entry.getInteger(0)); - assertEquals("c", entry.getString(1)); + try (IV8ValueIterator iterator = v8ValueMap.getKeys()) { + assertEquals("x", ((V8ValueString) iterator.getNext()).getValue()); + assertEquals("y", ((V8ValueString) iterator.getNext()).getValue()); + assertEquals(3, ((V8ValueInteger) iterator.getNext()).getValue()); + } + try (IV8ValueIterator iterator = v8ValueMap.getValues()) { + assertEquals(1, ((V8ValueInteger) iterator.getNext()).getValue()); + assertEquals("b", ((V8ValueString) iterator.getNext()).getValue()); + assertEquals("c", ((V8ValueString) iterator.getNext()).getValue()); + } + try (IV8ValueIterator iterator = v8ValueMap.getEntries()) { + try (V8ValueArray entry = iterator.getNext()) { + assertEquals("x", entry.getString(0)); + assertEquals(1, entry.getInteger(1)); + } + try (V8ValueArray entry = iterator.getNext()) { + assertEquals("y", entry.getString(0)); + assertEquals("b", entry.getString(1)); + } + try (V8ValueArray entry = iterator.getNext()) { + assertEquals(3, entry.getInteger(0)); + assertEquals("c", entry.getString(1)); + } } assertEquals(1, v8Runtime.getReferenceCount()); } diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueObject.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueObject.java index b18bf0de0..7793215b8 100644 --- a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueObject.java +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueObject.java @@ -51,6 +51,28 @@ public void testClearWeak() throws JavetException { } } + @Test + public void testEquals() throws JavetException { + try (V8ValueObject v8ValueObject1 = v8Runtime.getExecutor( + "const a = {'x': '1'}; a;").execute()) { + assertFalse(v8ValueObject1.equals(null)); + assertFalse(v8ValueObject1.sameValue(null)); + assertFalse(v8ValueObject1.strictEquals(null)); + assertFalse(v8ValueObject1.equals(v8Runtime.createV8ValueNull())); + assertFalse(v8ValueObject1.sameValue(v8Runtime.createV8ValueNull())); + assertFalse(v8ValueObject1.strictEquals(v8Runtime.createV8ValueNull())); + assertTrue(v8ValueObject1.equals(v8ValueObject1)); + assertTrue(v8ValueObject1.sameValue(v8ValueObject1)); + assertTrue(v8ValueObject1.strictEquals(v8ValueObject1)); + try (V8ValueObject v8ValueObject2 = v8Runtime.getExecutor( + "const b = {'x': '1'}; b;").execute()) { + assertFalse(v8ValueObject1.equals(v8ValueObject2)); + assertFalse(v8ValueObject1.sameValue(v8ValueObject2)); + assertFalse(v8ValueObject1.strictEquals(v8ValueObject2)); + } + } + } + @Test public void testGetOwnPropertyNames() throws JavetException { try (V8ValueObject v8ValueObject = v8Runtime.getExecutor( @@ -118,9 +140,9 @@ public void testGetProperty() throws JavetException { assertEquals(3L, ((V8ValueLong) v8ValueObject.getProperty("c")).getValue()); assertEquals(3L, v8ValueObject.getPropertyLong("c")); assertEquals(1, v8ValueObject.getPropertyInteger("d")); - assertTrue(v8ValueObject.getProperty("e") instanceof V8ValueNull); + assertTrue(v8ValueObject.getProperty("e").isNull()); assertEquals("測試", v8ValueObject.getPropertyString("中文")); - assertTrue(v8ValueObject.getProperty("$") instanceof V8ValueUndefined); + assertTrue(v8ValueObject.getProperty("$").isUndefined()); assertEquals(1, v8Runtime.getReferenceCount()); try (V8ValueObject childV8ValueObject = v8ValueObject.getProperty("g")) { assertNotNull(childV8ValueObject); @@ -142,6 +164,13 @@ public void testGetProperty() throws JavetException { } } + @Test + public void testIdentityHash() throws JavetException { + try (V8ValueObject v8ValueObject = v8Runtime.getExecutor("const a = {}; a;").execute()) { + assertTrue(v8ValueObject.getIdentityHash() > 0); + } + } + @Test public void testInvoke() throws JavetException { try (V8ValueArray v8ValueArray = v8Runtime.getExecutor("const a = [1, 2, 3]; a;").execute()) { @@ -244,7 +273,7 @@ public void testSetWeakDirectDescendant() throws JavetException { v8Runtime.requestGarbageCollectionForTesting(true); assertEquals(0, v8Runtime.getReferenceCount()); assertEquals(0L, a.getHandle()); - assertTrue(globalObject.get("a") instanceof V8ValueUndefined); + assertTrue(globalObject.get("a").isUndefined()); } @Test diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueSet.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueSet.java index ff0542375..bf47ce31c 100644 --- a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueSet.java +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueSet.java @@ -22,7 +22,6 @@ import com.caoccao.javet.values.V8Value; import com.caoccao.javet.values.primitive.V8ValueInteger; import com.caoccao.javet.values.primitive.V8ValueString; -import com.caoccao.javet.values.virtual.V8VirtualList; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -76,11 +75,10 @@ public void testHas() throws JavetException { assertNotNull(iV8ValueArray); assertEquals(0, iV8ValueArray.getLength()); } - try (V8VirtualList keys = v8ValueSet.getKeys()) { - assertEquals(3, keys.size()); - assertEquals("x", ((V8ValueString) keys.get(0)).getValue()); - assertEquals("y", ((V8ValueString) keys.get(1)).getValue()); - assertEquals(3, ((V8ValueInteger) keys.get(2)).getValue()); + try (IV8ValueIterator iterator = v8ValueSet.getKeys()) { + assertEquals("x", ((V8ValueString) iterator.getNext()).getValue()); + assertEquals("y", ((V8ValueString) iterator.getNext()).getValue()); + assertEquals(3, ((V8ValueInteger) iterator.getNext()).getValue()); } } } diff --git a/src/test/java/com/caoccao/javet/values/reference/TestV8ValueTypedArray.java b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueTypedArray.java new file mode 100644 index 000000000..ac7f1959f --- /dev/null +++ b/src/test/java/com/caoccao/javet/values/reference/TestV8ValueTypedArray.java @@ -0,0 +1,272 @@ +/* + * 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.values.reference; + +import com.caoccao.javet.BaseTestJavetRuntime; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.values.V8ValueReferenceType; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestV8ValueTypedArray extends BaseTestJavetRuntime { + + @Test + public void testByte() throws JavetException { + final int length = 16; + int[] types = new int[]{V8ValueReferenceType.Int8Array, V8ValueReferenceType.Uint8Array, V8ValueReferenceType.Uint8ClampedArray}; + byte[] bytes = new byte[length]; + new Random().nextBytes(bytes); + for (int i = 0; i < length; i++) { + bytes[i] = (byte) (bytes[i] & 0x7F); + } + for (int i = 0; i < types.length; ++i) { + final int type = types[i]; + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(1, v8ValueTypedArray.getSizeInBytes()); + assertEquals(length, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + try (V8ValueArrayBuffer v8ValueArrayBuffer = v8ValueTypedArray.getBuffer()) { + v8ValueArrayBuffer.fromBytes(bytes); + } + for (int j = 0; j < length; j++) { + assertEquals(bytes[j], v8Runtime.getExecutor("a[" + j + "];").executeInteger()); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(1, v8ValueTypedArray.getSizeInBytes()); + assertEquals(length, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + try (V8ValueArrayBuffer v8ValueArrayBuffer = v8ValueTypedArray.getBuffer()) { + v8ValueArrayBuffer.fromBytes(bytes); + } + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int j = 0; j < length; j++) { + assertEquals(bytes[j], v8Runtime.getExecutor("b[" + j + "];").executeInteger()); + } + } + v8Runtime.unlock().resetContext().lock(); + } + } + + @Test + public void testDouble() throws JavetException { + final int length = 16; + final int size = 8; + final int type = V8ValueReferenceType.Float64Array; + final int byteLength = length * size; + final double[] doubles = new Random().doubles(length).toArray(); + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromDoubles(doubles)); + for (int i = 0; i < length; i++) { + assertEquals(doubles[i], v8Runtime.getExecutor("a[" + i + "];").executeDouble(), 0.001D); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromDoubles(doubles)); + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int i = 0; i < length; i++) { + assertEquals(doubles[i], v8Runtime.getExecutor("b[" + i + "];").executeDouble(), 0.001D); + } + } + } + + @Test + public void testFloat() throws JavetException { + final int length = 16; + final int size = 4; + final int type = V8ValueReferenceType.Float32Array; + final int byteLength = length * size; + final float[] floats = new float[length]; + Random random = new Random(); + for (int i = 0; i < length; i++) { + floats[i] = random.nextFloat(); + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromFloats(floats)); + for (int i = 0; i < length; i++) { + assertEquals(floats[i], v8Runtime.getExecutor("a[" + i + "];").executeDouble().floatValue(), 0.001F); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromFloats(floats)); + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int i = 0; i < length; i++) { + assertEquals(floats[i], v8Runtime.getExecutor("b[" + i + "];").executeDouble().floatValue(), 0.001F); + } + } + } + + @Test + public void testInteger() throws JavetException { + final int length = 16; + int[] sizes = new int[]{4, 4}; + int[] types = new int[]{V8ValueReferenceType.Int32Array, V8ValueReferenceType.Uint32Array}; + final int[] integers = new Random().ints(length).toArray(); + for (int i = 0; i < length; i++) { + if (integers[i] < 0) { + integers[i] = 0 - integers[i]; + } + } + for (int i = 0; i < types.length; ++i) { + final int size = sizes[i]; + final int type = types[i]; + final int byteLength = length * size; + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromIntegers(integers)); + for (int j = 0; j < length; j++) { + assertEquals(integers[j], v8Runtime.getExecutor("a[" + j + "];").executeInteger()); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromIntegers(integers)); + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int j = 0; j < length; j++) { + assertEquals(integers[j], v8Runtime.getExecutor("b[" + j + "];").executeInteger()); + } + } + v8Runtime.unlock().resetContext().lock(); + } + } + + @Test + public void testLong() throws JavetException { + final int length = 16; + int[] sizes = new int[]{8, 8}; + int[] types = new int[]{V8ValueReferenceType.BigInt64Array, V8ValueReferenceType.BigUint64Array}; + final long[] longs = new Random().longs(length).toArray(); + for (int i = 0; i < length; i++) { + if (longs[i] < 0) { + longs[i] = 0 - longs[i]; + } + } + for (int i = 0; i < types.length; ++i) { + final int size = sizes[i]; + final int type = types[i]; + final int byteLength = length * size; + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromLongs(longs)); + for (int j = 0; j < length; j++) { + assertEquals(longs[j], v8Runtime.getExecutor("a[" + j + "];").executeLong()); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromLongs(longs)); + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int j = 0; j < length; j++) { + assertEquals(longs[j], v8Runtime.getExecutor("b[" + j + "];").executeLong()); + } + } + v8Runtime.unlock().resetContext().lock(); + } + } + + @Test + public void testShort() throws JavetException { + final int length = 16; + int[] sizes = new int[]{2, 2}; + int[] types = new int[]{V8ValueReferenceType.Int16Array, V8ValueReferenceType.Uint16Array}; + final short[] shorts = new short[length]; + Random random = new Random(); + for (int i = 0; i < length; i++) { + shorts[i] = (short) (random.nextInt() & 0x7FFF); + } + for (int i = 0; i < types.length; ++i) { + final int size = sizes[i]; + final int type = types[i]; + final int byteLength = length * size; + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.getExecutor( + "const a = new " + V8ValueTypedArray.getName(type) + "(" + length + "); a;").execute()) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromShorts(shorts)); + for (int j = 0; j < length; j++) { + assertEquals(shorts[j], v8Runtime.getExecutor("a[" + j + "];").executeInteger()); + } + } + try (V8ValueTypedArray v8ValueTypedArray = v8Runtime.createV8ValueTypedArray(type, length)) { + assertEquals(length, v8ValueTypedArray.getLength()); + assertEquals(size, v8ValueTypedArray.getSizeInBytes()); + assertEquals(byteLength, v8ValueTypedArray.getByteLength()); + assertEquals(0, v8ValueTypedArray.getByteOffset()); + assertEquals(type, v8ValueTypedArray.getType()); + assertTrue(v8ValueTypedArray.fromShorts(shorts)); + v8Runtime.getGlobalObject().set("b", v8ValueTypedArray); + for (int j = 0; j < length; j++) { + assertEquals(shorts[j], v8Runtime.getExecutor("b[" + j + "];").executeInteger()); + } + } + v8Runtime.unlock().resetContext().lock(); + } + } +}