diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2c0b759..f1664dff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: test - run: bazel query --output=label 'kind("...", //...) except attr("tags", "cpp20", "//...")' | xargs bazel test --cxxopt='-Werror' --cxxopt='-std=c++17' --repo_env=CC=clang --test_output=errors + run: bazel test --cxxopt='-Werror' --cxxopt='-std=c++17' --repo_env=CC=clang --test_output=errors //implementation/legacy/... ubuntu-latest-cpp20: runs-on: ubuntu-latest @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: test - run: bazel query --output=label 'kind("...", //...) except attr(tags, "cpp20", //...)' | xargs bazel test --cxxopt='-Werror' --cxxopt='-std=c++17' --repo_env=CC=clang --test_output=errors + run: bazel test --cxxopt='-Werror' --cxxopt='-std=c++17' --repo_env=CC=clang --test_output=errors //implementation/legacy/... macos-latest-cpp20: runs-on: macos-latest diff --git a/.gitignore b/.gitignore index bd12961c..80835edd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ bazel* +.vscode diff --git a/BUILD b/BUILD index 7949e520..f3d79daf 100644 --- a/BUILD +++ b/BUILD @@ -82,6 +82,7 @@ cc_library( "//implementation/jni_helper:static_field_value", "//metaprogramming:corpus", "//metaprogramming:corpus_tag", + "//metaprogramming:string_literal", ], ) diff --git a/JNI_BIND_VERSION.inc b/JNI_BIND_VERSION.inc index 45a1b3f4..26aaba0e 100644 --- a/JNI_BIND_VERSION.inc +++ b/JNI_BIND_VERSION.inc @@ -1 +1 @@ -1.1.2 +1.2.0 diff --git a/README.md b/README.md index abd18193..494e2ab4 100755 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ If you're already using Bazel add the following to your WORKSPACE: ```starlark http_archive( name = "jni-bind", - urls = ["https://github.com/google/jni-bind/archive/refs/tags/Release-1.1.2-beta.zip"], - strip_prefix = "jni-bind-Release-1.1.2-beta", + urls = ["https://github.com/google/jni-bind/archive/refs/tags/Release-1.2.0-beta.zip"], + strip_prefix = "jni-bind-Release-1.2.0-beta", ) ``` diff --git a/implementation/BUILD b/implementation/BUILD index 5044af27..32f82380 100644 --- a/implementation/BUILD +++ b/implementation/BUILD @@ -923,8 +923,11 @@ cc_library( "//:jni_dep", "//implementation/jni_helper:lifecycle", "//metaprogramming:invocable_map", + "//metaprogramming:invocable_map_20", "//metaprogramming:queryable_map", + "//metaprogramming:queryable_map_20", "//metaprogramming:string_contains", + "//metaprogramming:string_literal", ], ) @@ -1071,7 +1074,7 @@ cc_test( deps = [ "//:jni_bind", "//:jni_test", - "//metaprogramming:concatenate", + "//metaprogramming:type_to_type_map", "@googletest//:gtest_main", ], ) @@ -1237,9 +1240,11 @@ cc_library( ":method_selection", ":no_idx", "//:jni_dep", - "//implementation/jni_helper:invoke_static", "//metaprogramming:invocable_map", + "//metaprogramming:invocable_map_20", "//metaprogramming:queryable_map", + "//metaprogramming:queryable_map_20", + "//metaprogramming:string_literal", ], ) diff --git a/implementation/array_test.cc b/implementation/array_test.cc index cc9858a7..025a3dd2 100644 --- a/implementation/array_test.cc +++ b/implementation/array_test.cc @@ -16,10 +16,7 @@ #include -#include -#include #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/array_type_conversion_test.cc b/implementation/array_type_conversion_test.cc index eb1084cd..731ff168 100644 --- a/implementation/array_type_conversion_test.cc +++ b/implementation/array_type_conversion_test.cc @@ -14,8 +14,8 @@ * limitations under the License. */ -#include -#include +#include + #include "jni_bind.h" namespace { diff --git a/implementation/array_view_test.cc b/implementation/array_view_test.cc index 8f1ff7ab..2403b8fc 100644 --- a/implementation/array_view_test.cc +++ b/implementation/array_view_test.cc @@ -14,6 +14,7 @@ * limitations under the License. */ #include +#include #include #include @@ -50,51 +51,43 @@ TEST_F(JniTest, ArrayView_CallsLengthProperly) { TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, ReleaseBooleanArrayElements( - Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, - ReleaseByteArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, - ReleaseCharArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL( - *env_, ReleaseShortArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, - ReleaseIntArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, - ReleaseLongArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL( - *env_, ReleaseFloatArrayElements(Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake()), + Eq(Fake()), 0)); EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, ReleaseDoubleArrayElements( - Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake()), + Eq(Fake()), 0)); LocalArray boolean_array{AdoptLocal{}, Fake()}; LocalArray byte_array{AdoptLocal{}, Fake()}; @@ -118,9 +111,8 @@ TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { TEST_F(JniTest, LocalArrayView_AllowsCTAD) { EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake()), _)) .WillOnce(::testing::Return(Fake())); - EXPECT_CALL(*env_, ReleaseBooleanArrayElements( - Eq(Fake()), - Eq(Fake()), 0)); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake()), + Eq(Fake()), 0)); LocalArray boolean_array{AdoptLocal{}, Fake()}; ArrayView ctad_array_view{boolean_array.Pin()}; @@ -326,7 +318,7 @@ TEST_F(JniTest, ArrayView_RichObjectsAreIterable) { int fake_result = 123; for (LocalObject obj : obj_view) { EXPECT_CALL(*env_, CallIntMethodV).WillOnce(::testing::Return(fake_result)); - EXPECT_EQ(obj("Foo"), fake_result); + EXPECT_EQ(obj.Call<"Foo">(), fake_result); fake_result++; } } diff --git a/implementation/class.h b/implementation/class.h index 81dee205..18a52196 100644 --- a/implementation/class.h +++ b/implementation/class.h @@ -36,7 +36,10 @@ namespace jni { template -struct Class {}; +struct Class { + constexpr Class() = default; + constexpr Class(const char* name) {} +}; template , // provided where they are and aren't present. //////////////////////////////////////////////////////////////////////////////// + // To stifle a test failure. + constexpr Class() + : Object("__JNI_BIND__NO_CLASS__"), + constructors_(Constructor<>{}), + static_(), + methods_(), + fields_() {} + // Methods + Fields. explicit constexpr Class(const char* class_name, Methods_... methods, Fields_... fields) diff --git a/implementation/class_loader_ref_second_test.cc b/implementation/class_loader_ref_second_test.cc index 50424733..de27b6dc 100644 --- a/implementation/class_loader_ref_second_test.cc +++ b/implementation/class_loader_ref_second_test.cc @@ -91,7 +91,7 @@ TEST_F(JniTestWithNoDefaultJvmRef, auto second_custom_loader_object = class_loader.BuildLocalObject(jint{2}); - EXPECT_EQ(custom_loader_object("methodNoCrossTalk", jint{2}), 123); + EXPECT_EQ(custom_loader_object.Call<"methodNoCrossTalk">(jint{2}), 123); TearDown(); } diff --git a/implementation/class_loader_ref_test.cc b/implementation/class_loader_ref_test.cc index 3868736e..c743b2fb 100644 --- a/implementation/class_loader_ref_test.cc +++ b/implementation/class_loader_ref_test.cc @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include #include #include @@ -119,7 +118,7 @@ TEST_F(JniTest, LocalObject_SupportsPassingAnObjectWithAClassLoader) { // LocalObject a{}; // doesn't compile (good). LocalObject a{Fake()}; LocalObject b{}; - b("Foo", a); + b.Call<"Foo">(a); default_globals_made_that_should_be_released_.clear(); } @@ -131,7 +130,7 @@ TEST_F(JniTestForClassLoaders, LocalClassLoader local_class_loader{Fake()}; auto a = local_class_loader.BuildLocalObject(); LocalObject b{a}; - b("Foo", a); + b.Call<"Foo">(a); default_globals_made_that_should_be_released_.clear(); TearDown(); @@ -144,7 +143,7 @@ TEST_F(JniTestForClassLoaders, LocalClassLoader local_class_loader{Fake()}; auto a = local_class_loader.BuildGlobalObject(); LocalObject b{a}; - b("Foo", a); + b.Call<"Foo">(a); default_globals_made_that_should_be_released_.clear(); TearDown(); @@ -161,7 +160,7 @@ TEST_F(JniTestForClassLoaders, LocalObject a = local_class_loader.BuildLocalObject(); LocalObject b{a}; - b("Foo", a); + b.Call<"Foo">(a); TearDown(); } @@ -173,7 +172,7 @@ TEST_F(JniTestForClassLoaders, ClassLoaderRefTest_ConstructsFromRValue) { LocalObject b{ local_class_loader.BuildLocalObject()}; - LocalObject c{b("Foo")}; + LocalObject c{b.Call<"Foo">()}; TearDown(); } @@ -187,7 +186,7 @@ TEST_F(JniTestForClassLoaders, LocalObject a{}; LocalObject b = local_class_loader.BuildLocalObject(a); - b("Foo", a); + b.Call<"Foo">(a); TearDown(); } @@ -236,7 +235,7 @@ TEST_F(JniTestWithNoDefaultJvmRef, LocalObject a = local_class_loader.BuildLocalObject(); LocalObject b{}; - b("Foo", a); + b.Call<"Foo">(a); TearDown(); } @@ -310,7 +309,7 @@ TEST_F(JniTestWithNoDefaultJvmRef, kDefaultConfiguration}; jni::LocalObject obj1{AdoptLocal{}, Fake(1)}; - obj1("Foo"); + obj1.Call<"Foo">(); this->TearDown(); } diff --git a/implementation/class_loader_test.cc b/implementation/class_loader_test.cc index 8ca0d6fe..29b3962c 100644 --- a/implementation/class_loader_test.cc +++ b/implementation/class_loader_test.cc @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/class_test.cc b/implementation/class_test.cc index ec490a2f..44bb0fbb 100644 --- a/implementation/class_test.cc +++ b/implementation/class_test.cc @@ -14,7 +14,6 @@ * limitations under the License. */ -#include #include #include "jni_bind.h" #include "jni_test.h" diff --git a/implementation/constructor_test.cc b/implementation/constructor_test.cc index 4e52042d..c51bb097 100644 --- a/implementation/constructor_test.cc +++ b/implementation/constructor_test.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include "jni_bind.h" #include "jni_test.h" diff --git a/implementation/field_ref_test.cc b/implementation/field_ref_test.cc index a6bf521a..2ae6be6f 100644 --- a/implementation/field_ref_test.cc +++ b/implementation/field_ref_test.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include #include @@ -59,8 +59,8 @@ TEST_F(JniTest, Field_SimpleGetAndSet) { Field("SomeField", jint{})}; GlobalObject obj{}; - EXPECT_EQ(999, obj["SomeField"].Get()); - obj["SomeField"].Set(123); + EXPECT_EQ(999, obj.Access<"SomeField">().Get()); + obj.Access<"SomeField">().Set(123); } TEST_F(JniTest, Field_BooleanField) { @@ -68,8 +68,8 @@ TEST_F(JniTest, Field_BooleanField) { EXPECT_CALL(*env_, SetBooleanField); LocalObject obj{}; - obj["booleanField"].Get(); - obj["booleanField"].Set(true); + obj.Access<"booleanField">().Get(); + obj.Access<"booleanField">().Set(true); } TEST_F(JniTest, Field_ByteField) { @@ -77,8 +77,8 @@ TEST_F(JniTest, Field_ByteField) { EXPECT_CALL(*env_, SetByteField); LocalObject obj{}; - obj["byteField"].Get(); - obj["byteField"].Set(jbyte{123}); + obj.Access<"byteField">().Get(); + obj.Access<"byteField">().Set(jbyte{123}); } TEST_F(JniTest, Field_CharField) { @@ -86,8 +86,8 @@ TEST_F(JniTest, Field_CharField) { EXPECT_CALL(*env_, SetCharField); LocalObject obj{}; - obj["charField"].Get(); - obj["charField"].Set(jchar{'a'}); + obj.Access<"charField">().Get(); + obj.Access<"charField">().Set(jchar{'a'}); } TEST_F(JniTest, Field_ShortField) { @@ -95,8 +95,8 @@ TEST_F(JniTest, Field_ShortField) { EXPECT_CALL(*env_, SetShortField); LocalObject obj{}; - obj["shortField"].Get(); - obj["shortField"].Set(jshort{123}); + obj.Access<"shortField">().Get(); + obj.Access<"shortField">().Set(jshort{123}); } TEST_F(JniTest, Field_intField) { @@ -104,8 +104,8 @@ TEST_F(JniTest, Field_intField) { EXPECT_CALL(*env_, SetIntField); LocalObject obj{}; - obj["intField"].Get(); - obj["intField"].Set(123); + obj.Access<"intField">().Get(); + obj.Access<"intField">().Set(123); } TEST_F(JniTest, Field_longField) { @@ -113,24 +113,24 @@ TEST_F(JniTest, Field_longField) { EXPECT_CALL(*env_, SetLongField); LocalObject obj{}; - obj["longField"].Get(); - obj["longField"].Set(123); + obj.Access<"longField">().Get(); + obj.Access<"longField">().Set(123); } TEST_F(JniTest, Field_floatField) { LocalObject obj{}; EXPECT_CALL(*env_, GetFloatField); EXPECT_CALL(*env_, SetFloatField); - obj["floatField"].Get(); - obj["floatField"].Set(123.f); + obj.Access<"floatField">().Get(); + obj.Access<"floatField">().Set(123.f); } TEST_F(JniTest, Field_doubleField) { LocalObject obj{}; EXPECT_CALL(*env_, GetDoubleField); EXPECT_CALL(*env_, SetDoubleField); - obj["doubleField"].Get(); - obj["doubleField"].Set(123.); + obj.Access<"doubleField">().Get(); + obj.Access<"doubleField">().Set(123.); } TEST_F(JniTest, Field_ObjectGet) { @@ -144,7 +144,7 @@ TEST_F(JniTest, Field_ObjectGet) { static constexpr Class kClass2{"kClass2"}; LocalObject obj{}; - LocalObject obj2 = obj["classField"].Get(); + LocalObject obj2 = obj.Access<"classField">().Get(); } TEST_F(JniTest, Field_ObjectSet) { @@ -161,10 +161,11 @@ TEST_F(JniTest, Field_ObjectSet) { LocalObject obj{}; LocalObject some_obj{AdoptLocal{}, Fake()}; - obj["classField"].Set(Fake()); - obj["classField"].Set(LocalObject{AdoptLocal{}, Fake()}); - obj["classField"].Set(some_obj); - obj["classField"].Set(std::move(some_obj)); + obj.Access<"classField">().Set(Fake()); + obj.Access<"classField">().Set( + LocalObject{AdoptLocal{}, Fake()}); + obj.Access<"classField">().Set(some_obj); + obj.Access<"classField">().Set(std::move(some_obj)); } } // namespace diff --git a/implementation/global_object_test.cc b/implementation/global_object_test.cc index 7e19f79d..9669307b 100644 --- a/implementation/global_object_test.cc +++ b/implementation/global_object_test.cc @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include +#include #include #include @@ -200,15 +200,15 @@ TEST_F(JniTest, GlobalObject_ValuesWorkAfterMoveConstructor) { Method{"Foo", jni::Return{}, Params{}}, Field{"BarField", jint{}}}; GlobalObject global_object_1{AdoptGlobal{}, Fake(1)}; - global_object_1("Foo", 1); - global_object_1("Foo", 2); - global_object_1["BarField"].Set(1); + global_object_1.Call<"Foo">(1); + global_object_1.Call<"Foo">(2); + global_object_1.Access<"BarField">().Set(1); GlobalObject global_object_2{std::move(global_object_1)}; - global_object_2("Foo", 3); - global_object_2["BarField"].Set(2); - global_object_2["BarField"].Set(3); - global_object_2["BarField"].Set(4); + global_object_2.Call<"Foo">(3); + global_object_2.Access<"BarField">().Set(2); + global_object_2.Access<"BarField">().Set(3); + global_object_2.Access<"BarField">().Set(4); GlobalObject global_object_3{AdoptGlobal{}, Fake(1)}; } @@ -245,8 +245,8 @@ TEST_F(JniTest, GlobalObject_ObjectReturnsInstanceMethods) { Params{}}}; GlobalObject global_object{}; - global_object("Foo", 1); - global_object("Baz", 2.f); + global_object.Call<"Foo">(1); + global_object.Call<"Baz">(2.f); global_object( "AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", int{}, float{}, int{}, float{}, double{}); @@ -268,7 +268,7 @@ TEST_F(JniTest, GlobalObject_SupportsPassingAPrvalue) { GlobalObject a{}; GlobalObject b{}; - b("Foo", std::move(a)); + b.Call<"Foo">(std::move(a)); } TEST_F(JniTest, GlobalObjects_PromoteRValuesFromEmittedLValues) { @@ -277,9 +277,9 @@ TEST_F(JniTest, GlobalObjects_PromoteRValuesFromEmittedLValues) { "TestClass2", Method{"Foo", jni::Return{kClass1}, jni::Params{}}}; LocalObject b{}; - GlobalObject a{b("Foo")}; + GlobalObject a{b.Call<"Foo">()}; - a = b("Foo"); + a = b.Call<"Foo">(); } } // namespace diff --git a/implementation/id_test.cc b/implementation/id_test.cc index b5826a7f..979fb1bf 100644 --- a/implementation/id_test.cc +++ b/implementation/id_test.cc @@ -16,8 +16,6 @@ #include -#include -#include #include "jni_bind.h" namespace { diff --git a/implementation/jni_type_test.cc b/implementation/jni_type_test.cc index df2d2d7f..8c1b55b4 100644 --- a/implementation/jni_type_test.cc +++ b/implementation/jni_type_test.cc @@ -15,8 +15,6 @@ */ #include -#include -#include #include "jni_bind.h" namespace { diff --git a/implementation/jvm_test.cc b/implementation/jvm_test.cc index 0cfed6fe..66008e7e 100644 --- a/implementation/jvm_test.cc +++ b/implementation/jvm_test.cc @@ -14,14 +14,8 @@ * limitations under the License. */ -#include - -#include #include -#include "implementation/jni_helper/jni_env.h" -#include "implementation/jni_helper/jni_helper.h" #include "jni_bind.h" -#include "jni_test.h" namespace { diff --git a/implementation/legacy/BUILD b/implementation/legacy/BUILD new file mode 100644 index 00000000..76591293 --- /dev/null +++ b/implementation/legacy/BUILD @@ -0,0 +1,245 @@ +package( + licenses = ["notice"], +) + +################################################################################ +# Array. +################################################################################ +cc_test( + name = "array_view_test", + srcs = ["array_view_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# ClassLoader. +################################################################################ +cc_test( + name = "class_loader_ref_test", + srcs = ["class_loader_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation:configuration", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "class_loader_ref_second_test", + srcs = ["class_loader_ref_second_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Field. +################################################################################ +cc_test( + name = "field_ref_test", + srcs = ["field_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# GlobalObject. +################################################################################ +cc_test( + name = "global_object_test", + srcs = ["global_object_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# LocalArray. +################################################################################ +cc_test( + name = "local_array_field_multidimensional_test", + srcs = ["local_array_field_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_field_test", + srcs = ["local_array_field_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_iteration_test", + srcs = ["local_array_iteration_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_multidimensional_test", + srcs = ["local_array_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_test", + srcs = ["local_array_method_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_method_multidimensional_test", + srcs = ["local_array_method_multidimensional_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "local_array_string_test", + srcs = ["local_array_string_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# LocalObject. +################################################################################ +cc_test( + name = "local_object_test", + srcs = ["local_object_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Method. +################################################################################ +cc_test( + name = "method_ref_test", + srcs = ["method_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Multi type test. +################################################################################ +cc_test( + name = "multi_type_test", + srcs = ["multi_type_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Object. +################################################################################ +cc_test( + name = "overload_ref_test", + srcs = ["overload_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Static. +################################################################################ +cc_test( + name = "static_ref_test", + srcs = ["static_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "string_ref_test", + srcs = ["string_ref_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "//implementation/jni_helper:fake_test_constants", + "@googletest//:gtest_main", + ], +) + +################################################################################ +# Self. +################################################################################ +cc_test( + name = "self_test", + srcs = ["self_test.cc"], + deps = [ + "//:jni_bind", + "//:jni_test", + "@googletest//:gtest_main", + ], +) diff --git a/implementation/legacy/README.md b/implementation/legacy/README.md new file mode 100644 index 00000000..1677833d --- /dev/null +++ b/implementation/legacy/README.md @@ -0,0 +1,3 @@ +This directory contains a copy of the tests from implementation but using the legacy `obj.Foo("methodName", args...);` syntax. + +These tests are effectively pure duplication and may be removed at some future point. They are here to maintain coverage as JNI Bind migrates to a C++20 friendly syntax. diff --git a/implementation/legacy/array_view_test.cc b/implementation/legacy/array_view_test.cc new file mode 100644 index 00000000..502f0bb8 --- /dev/null +++ b/implementation/legacy/array_view_test.cc @@ -0,0 +1,443 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::ArrayView; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Return; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; + +//////////////////////////////////////////////////////////////////////////////// +// Pin Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_CallsLengthProperly) { + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + + LocalArray local_int_array{5}; + EXPECT_EQ(local_int_array.Length(), 3); +} + +TEST_F(JniTest, ArrayView_GetsAndReleaseArrayBuffer) { + EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetByteArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetCharArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetShortArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetIntArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetLongArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetFloatArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + EXPECT_CALL(*env_, GetDoubleArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + LocalArray boolean_array{AdoptLocal{}, Fake()}; + LocalArray byte_array{AdoptLocal{}, Fake()}; + LocalArray char_array{AdoptLocal{}, Fake()}; + LocalArray short_array{AdoptLocal{}, Fake()}; + LocalArray int_array{AdoptLocal{}, Fake()}; + LocalArray long_array{AdoptLocal{}, Fake()}; + LocalArray float_array{AdoptLocal{}, Fake()}; + LocalArray double_array{AdoptLocal{}, Fake()}; + + ArrayView boolean_array_pin = {boolean_array.Pin()}; + ArrayView byte_array_pin = {byte_array.Pin()}; + ArrayView int_array_pin = {int_array.Pin()}; + ArrayView char_array_pin = {char_array.Pin()}; + ArrayView short_array_pin = {short_array.Pin()}; + ArrayView long_array_pin = {long_array.Pin()}; + ArrayView float_array_pin = {float_array.Pin()}; + ArrayView double_array_pin = {double_array.Pin()}; +} + +TEST_F(JniTest, LocalArrayView_AllowsCTAD) { + EXPECT_CALL(*env_, GetBooleanArrayElements(Eq(Fake()), _)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Eq(Fake()), + Eq(Fake()), 0)); + + LocalArray boolean_array{AdoptLocal{}, Fake()}; + ArrayView ctad_array_view{boolean_array.Pin()}; + + // Despite supporting construction from xvalue, move ctor is deleted (good). + // ArrayView ctad_array_view_2 {std::move(ctad_array_view)}; +} + +TEST_F(JniTest, ArrayView_ConstructsFromAnObject) { + static constexpr Class kClass{"kClass"}; + LocalArray local_obj_array{1, LocalObject{}}; +} + +TEST_F(JniTest, ArrayView_ConstructsFromAnObjectRValueWithCTAD) { + static constexpr Class kClass{"kClass"}; + LocalArray local_obj_array{1, LocalObject{}}; +} + +TEST_F(JniTest, ArrayView_GetsAnObject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, GetObjectArrayElement(_, _)); + LocalArray local_obj_array{1, LocalObject{}}; + local_obj_array.Get(0); +} + +TEST_F(JniTest, ArrayView_GetsAnObjectWithCTAD) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, GetObjectArrayElement(_, _)); + LocalArray local_obj_array{1, LocalObject{}}; + local_obj_array.Get(0); +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Primitives. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_BooleanIsIterable) { + std::array fake_vals{jboolean{true}, jboolean{false}, jboolean{true}}; + EXPECT_CALL(*env_, NewBooleanArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetBooleanArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_ByteIsIterable) { + std::array fake_vals{jbyte{true}, jbyte{false}, jbyte{true}}; + EXPECT_CALL(*env_, NewByteArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(testing::Return(3)); + EXPECT_CALL(*env_, GetByteArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_CharIsIterable) { + std::array fake_vals{jchar{true}, jchar{false}, jchar{true}}; + EXPECT_CALL(*env_, NewCharArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetCharArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_ShortIsIterable) { + std::array fake_vals{jshort{true}, jshort{false}, jshort{true}}; + EXPECT_CALL(*env_, NewShortArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetShortArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_IntIsIterable) { + std::array fake_vals{jint{1}, jint{2}, jint{3}}; + + EXPECT_CALL(*env_, NewIntArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray int_arr{3}; + ArrayView int_view = int_arr.Pin(); + + EXPECT_TRUE(std::equal(int_view.begin(), int_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_LongIsIterable) { + std::array fake_vals{jlong{true}, jlong{false}, jlong{true}}; + EXPECT_CALL(*env_, NewLongArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetLongArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_FloatIsIterable) { + std::array fake_vals{jfloat{true}, jfloat{false}, jfloat{true}}; + + EXPECT_CALL(*env_, NewFloatArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetFloatArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_DoubleIsIterable) { + std::array fake_vals{jdouble{true}, jdouble{false}, jdouble{true}}; + EXPECT_CALL(*env_, NewDoubleArray(3)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetDoubleArrayElements) + .WillOnce(::testing::Return(fake_vals.data())); + + LocalArray bool_arr{3}; + ArrayView bool_view = bool_arr.Pin(); + + EXPECT_TRUE(std::equal(bool_view.begin(), bool_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Objects. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_ShallowObjectsAreIterable) { + std::array fake_vals{Fake(1), Fake(2), Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(1))) + .WillOnce(::testing::Return(Fake(2))) + .WillOnce(::testing::Return(Fake(3))); + + LocalArray obj_arr{AdoptLocal{}, Fake()}; + ArrayView obj_view = obj_arr.Pin(); + + EXPECT_TRUE(std::equal(obj_view.begin(), obj_view.end(), fake_vals.begin(), + fake_vals.end())); +} + +TEST_F(JniTest, ArrayView_RichObjectsAreIterable) { + static constexpr Class kClass{"kClass", Method{"Foo", Return{}}}; + + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(1))) + .WillOnce(::testing::Return(Fake(2))) + .WillOnce(::testing::Return(Fake(3))); + + LocalArray obj_arr{AdoptLocal{}, Fake()}; + auto obj_view = obj_arr.Pin(); + + // Note: GlobalObject will fail to compile here. This is good, the user + // should be forced to explicitly promote the local. + int fake_result = 123; + for (LocalObject obj : obj_view) { + EXPECT_CALL(*env_, CallIntMethodV).WillOnce(::testing::Return(fake_result)); + EXPECT_EQ(obj("Foo"), fake_result); + fake_result++; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Rank 2 Iterations. +// +// Note: Writing through every type would be tedious, however, if these tests +// could be generalised across the universe of types it would be better. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_Rank2IntArraysAreIterable) { + std::array fake_vals{Fake(1), Fake(2), + Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 0)) + .WillOnce(::testing::Return(Fake(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 1)) + .WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 2)) + .WillOnce(::testing::Return(Fake(3))); + + LocalArray int_arr_rank_2{AdoptLocal{}, Fake()}; + ArrayView int_rank2_view = int_arr_rank_2.Pin(); + + EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(), + fake_vals.begin(), fake_vals.end())); + + /* + // Also viable to write this: + // for (LocalArray jint_array : int_rank2_view) { } + */ +} + +TEST_F(JniTest, ArrayView_Rank2ObjectkArraysAreIterable) { + std::array fake_vals{Fake(1), Fake(2), + Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 0)) + .WillOnce(::testing::Return(Fake(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 1)) + .WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 2)) + .WillOnce(::testing::Return(Fake(3))); + + LocalArray int_arr_rank_2{AdoptLocal{}, Fake()}; + ArrayView int_rank2_view = int_arr_rank_2.Pin(); + + EXPECT_TRUE(std::equal(int_rank2_view.begin(), int_rank2_view.end(), + fake_vals.begin(), fake_vals.end())); + + /* + // Also viable to write this: + // for (LocalArray jint_array : int_rank2_view) { } + */ +} + +//////////////////////////////////////////////////////////////////////////////// +// Iteration Tests: Rank 3 Iterations. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ArrayView_Rank3IntArraysAreIterable) { + std::array fake_vals{Fake(), Fake(), + Fake()}; + + EXPECT_CALL(*env_, GetArrayLength(Fake())) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 0)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 1)) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 2)) + .WillOnce(::testing::Return(Fake())); + + LocalArray int_arr_rank_3{AdoptLocal{}, Fake()}; + ArrayView int_rank_3_view = int_arr_rank_3.Pin(); + + EXPECT_TRUE(std::equal(int_rank_3_view.begin(), int_rank_3_view.end(), + fake_vals.begin(), fake_vals.end())); + + // Also viable to write this: + // for (LocalArray jint_array : int_rank_3_view) { } +} + +TEST_F(JniTest, ArrayView_Rank3ObjectkArraysAreIterable) { + std::array fake_vals{Fake(1), Fake(2), + Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength(Fake(0))) + .WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 0)) + .WillOnce(::testing::Return(Fake(1))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 1)) + .WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, GetObjectArrayElement( + AsNewLocalReference(Fake()), 2)) + .WillOnce(::testing::Return(Fake(3))); + + LocalArray object_arr_rank_3{AdoptLocal{}, Fake(0)}; + ArrayView object_rank_3_view = object_arr_rank_3.Pin(); + + EXPECT_TRUE(std::equal(object_rank_3_view.begin(), object_rank_3_view.end(), + fake_vals.begin(), fake_vals.end())); + + // Also viable to write this: + // for (LocalArray jobject_array : object_rank_3_view) { } +} + +} // namespace diff --git a/implementation/legacy/class_loader_ref_second_test.cc b/implementation/legacy/class_loader_ref_second_test.cc new file mode 100644 index 00000000..50424733 --- /dev/null +++ b/implementation/legacy/class_loader_ref_second_test.cc @@ -0,0 +1,99 @@ +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::ClassLoader; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::Jvm; +using ::jni::JvmRef; +using ::jni::kNullClassLoader; +using ::jni::LocalClassLoader; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::SupportedClassSet; +using ::jni::test::AsGlobal; +using ::jni::test::JniTestWithNoDefaultJvmRef; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +// This test is isolated to correctly observe querying to Class + ClassLoader +// class definitions. Ids are now cached statically against their name, so this +// results in crosstalk. +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_ClassLoadersDoNotConflict) { + static constexpr Class kClass{ + "com/google/kClass", Constructor{}, + Method{"methodNoCrossTalk", Return{}, Params{}}}; + static constexpr ClassLoader kClassLoader{kNullClassLoader, + SupportedClassSet{kClass}}; + + // We will use this ClassLoader instead of the default loader to load + // Class. + static constexpr Jvm kClassLoaderJvm{kClassLoader}; + + InSequence sequence; + + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader"))) + .WillOnce(testing::Return(Fake(1))); + + EXPECT_CALL(*env_, NewGlobalRef(Fake(1))) + .WillOnce(testing::Return(AsGlobal(Fake(1)))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake(1)), StrEq("loadClass"), + StrEq("(Ljava/lang/String;)Ljava/lang/Class;"))) + .WillOnce(testing::Return(Fake(1))); + + EXPECT_CALL(*env_, NewStringUTF(_)) + .WillOnce(testing::Return(Fake())); + + // We should only try to load the class once even if we create multiple + // instances. + EXPECT_CALL(*env_, CallObjectMethodV(Fake(1), Fake(1), _)) + .WillOnce(testing::Return(Fake(2))); + + EXPECT_CALL(*env_, NewGlobalRef(Fake(2))) + .WillOnce(testing::Return(AsGlobal(Fake(2)))); + EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake(2)), StrEq(""), + StrEq("(I)V"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, + NewObjectV(AsGlobal(Fake(2)), Fake(2), _)) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, + NewObjectV(AsGlobal(Fake(2)), Fake(2), _)) + .WillOnce(testing::Return(Fake(3))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake(2)), StrEq("methodNoCrossTalk"), + + StrEq("(I)I"))) + .WillOnce(testing::Return(Fake(3))); + EXPECT_CALL(*env_, CallIntMethodV(Fake(2), Fake(3), _)) + .WillOnce(testing::Return(123)); + + // Code under test. + JvmRef jvm_ref{jvm_.get()}; + LocalClassLoader class_loader{ + AdoptLocal{}, Fake(1)}; + + auto custom_loader_object = class_loader.BuildLocalObject(jint{1}); + + auto second_custom_loader_object = + class_loader.BuildLocalObject(jint{2}); + + EXPECT_EQ(custom_loader_object("methodNoCrossTalk", jint{2}), 123); + + TearDown(); +} + +} // namespace diff --git a/implementation/legacy/class_loader_ref_test.cc b/implementation/legacy/class_loader_ref_test.cc new file mode 100644 index 00000000..9414665d --- /dev/null +++ b/implementation/legacy/class_loader_ref_test.cc @@ -0,0 +1,317 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ +#include + +#include +#include +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::ClassLoader; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::GlobalClassLoader; +using ::jni::Jvm; +using ::jni::JvmRef; +using ::jni::kDefaultJvm; +using ::jni::kNullClassLoader; +using ::jni::LocalClassLoader; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::PromoteToGlobal; +using ::jni::Return; +using ::jni::SupportedClassSet; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::jni::test::JniTestWithNoDefaultJvmRef; +using ::jni::test::kDefaultConfiguration; +using ::testing::_; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass1{"Class1", Constructor{}, Constructor{}, + Method{"Foo", Return{Class{"Class2"}}}}; + +static constexpr Class kClass2{ + "Class2", Constructor{}, Constructor{kClass1}, + Method{"Foo", jni::Return{}, jni::Params{kClass1}}}; + +static constexpr Class kClass3{"Class3"}; +static constexpr Class kClass4{"Class4"}; + +static constexpr ClassLoader kClassLoader{kNullClassLoader, + SupportedClassSet{kClass1, kClass2}}; + +static constexpr Jvm kJvm{kClassLoader}; + +// Helper class for classloader tests that gives the standard default class +// object when using CallObjectMethodV. This is because objects built from +// class loaders are built by calling "loadClass" on the respective classloader +// instance. JniTest is strict about the number of DeleteGlobalRef calls, +// so this satisfies that requirement. +// +// Note, when using this, you must call |TearDown| to pre-empt and class +// teardown prior to expectations being set. +class JniTestForClassLoaders : public JniTestWithNoDefaultJvmRef { + void SetUp() override { + JniTestWithNoDefaultJvmRef::SetUp(); + + ON_CALL(*env_, CallObjectMethodV) + .WillByDefault(::testing::Return(Fake())); + } +}; + +TEST_F(JniTest, LocalsAreMoveable) { + LocalClassLoader obj_1{Fake()}; + LocalClassLoader obj_2{std::move(obj_1)}; +} + +TEST_F(JniTest, GlobalClassLoadersSupportAdoptionMechanics) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + GlobalClassLoader obj_1{AdoptGlobal{}, Fake()}; +} + +TEST_F(JniTest, GlobalClassLoadersSupportPromoteMechanics) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + GlobalClassLoader obj_1{PromoteToGlobal{}, + Fake()}; +} + +TEST_F(JniTest, GlobalsAreMoveable) { + GlobalClassLoader obj_1{AdoptGlobal{}, Fake()}; + GlobalClassLoader obj_2{std::move(obj_1)}; +} + +//////////////////////////////////////////////////////////////////////////////// +// Default JVM, non-default classloader (No ID teardown on JVM destruction). +// +// Because these tests use |jni::kDefaultJvm|, the global refs bound to class +// and method IDs won't be freed (they are static across the process). As a +// hack, skip testing for them by calling clearing expected globals. +// +// An alternate (more effective) emulation would be to have these tests run +// as independent processes which would reflect the static nature of the memory. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectWithAClassLoader) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + // LocalObject a{}; // doesn't compile (good). + LocalObject a{Fake()}; + LocalObject b{}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); +} + +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_ConstructorsAcceptClassLoadedObjects) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + auto a = local_class_loader.BuildLocalObject(); + LocalObject b{a}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); + TearDown(); +} + +TEST_F(JniTestForClassLoaders, + lassLoaderRefTest_ConstructorsAcceptGlobalClassLoadedObjects) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + auto a = local_class_loader.BuildGlobalObject(); + LocalObject b{a}; + b("Foo", a); + + default_globals_made_that_should_be_released_.clear(); + TearDown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Non standard JVM, non-default classloader (ID teardown on JVM destruction). +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_DefaultLoadedObjectBuildsWithClassLoadedObject) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + LocalClassLoader local_class_loader{AdoptLocal{}, + Fake()}; + LocalObject a = + local_class_loader.BuildLocalObject(); + LocalObject b{a}; + b("Foo", a); + + TearDown(); +} + +TEST_F(JniTestForClassLoaders, ClassLoaderRefTest_ConstructsFromRValue) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + LocalObject b{ + local_class_loader.BuildLocalObject()}; + + LocalObject c{b("Foo")}; + + TearDown(); +} + +TEST_F(JniTestForClassLoaders, + ClassLoaderRefTest_ClassLoadedObjectBuildsWithDefaultLoadedObject) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + + LocalObject a{}; + LocalObject b = + local_class_loader.BuildLocalObject(a); + b("Foo", a); + + TearDown(); +} + +TEST_F( + JniTestForClassLoaders, + ClassLoaderRefTest_LocalClassLoaderWithSingleClassAndConstructorCompiles) { + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + auto a = local_class_loader.BuildLocalObject(12345); + // local_class_loader.BuildLocalObject(); doesn't compile (good) + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_LocalClassLoaderWithMultipleClassesCompiles) { + ON_CALL(*env_, CallObjectMethodV) + .WillByDefault(::testing::Return(Fake())); + + static constexpr ClassLoader kClassLoader{ + kNullClassLoader, SupportedClassSet{kClass1, kClass2, kClass3, kClass4}}; + static constexpr Jvm kJvm{kClassLoader}; + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + + LocalObject a{local_class_loader.BuildLocalObject()}; + auto b = local_class_loader.BuildLocalObject(); + auto c = local_class_loader.BuildLocalObject(); + auto d = local_class_loader.BuildLocalObject(); + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + DefaultLoadedObjectAcceptsClassLoadedObject) { + ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_)) + .WillByDefault(::testing::Return(Fake())); + + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + + LocalObject a = + local_class_loader.BuildLocalObject(); + LocalObject b{}; + b("Foo", a); + + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_DefaultLoadedClassCompiles) { + ON_CALL(*env_, CallObjectMethodV(testing::_, testing::_, testing::_)) + .WillByDefault(::testing::Return(Fake())); + JvmRef jvm_ref{jvm_.get(), kDefaultConfiguration}; + + LocalClassLoader local_class_loader{Fake()}; + + LocalObject a{local_class_loader.BuildLocalObject()}; + TearDown(); +} + +TEST_F(JniTestWithNoDefaultJvmRef, + ClassLoaderRefTest_ClassesOfDifferentClassLoadersAreUnique) { + static constexpr Class class_under_test{ + "com/google/ARCore", + Method{"Foo", jni::Return{}}, + }; + static constexpr ClassLoader class_loader{ + kNullClassLoader, SupportedClassSet{class_under_test}}; + + static constexpr jni::Jvm atypical_jvm_definition{class_loader}; + + InSequence seq; + + // The java/lang/Class and java/lang/ClassLoader will always be from the + // default loader, and they only need to be cached once. + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/Class"))) + .WillOnce(testing::Return(Fake(2))); + + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/ClassLoader"))) + .WillOnce(testing::Return(Fake(3))); + + EXPECT_CALL(*env_, GetObjectClass(Fake(1))) + .WillOnce(testing::Return(Fake(1))); + + EXPECT_CALL(*env_, + GetMethodID(AsGlobal(Fake(2)), StrEq("getClassLoader"), + StrEq("()Ljava/lang/ClassLoader;"))) + .WillOnce(testing::Return(Fake(2))); + + EXPECT_CALL(*env_, CallObjectMethodV(Fake(1), Fake(2), _)) + .WillOnce(testing::Return(Fake(3))); + + EXPECT_CALL(*env_, + GetMethodID(Eq(AsGlobal(Fake(3))), StrEq("loadClass"), + StrEq("(Ljava/lang/String;)Ljava/lang/Class;"))) + .WillOnce(testing::Return(Fake(1))); + + // Note: While "/" is the mandatory delimiter for describing the class in its + // definition, load class uses "." delineation. Strangely, when calling + // Classloader.loadClass on Android both '.' and '/'work, but on x86 Java (and + // presumably other JVM implementations), only the "." is accepted. + EXPECT_CALL(*env_, NewStringUTF(StrEq("com.google.ARCore"))) + .WillOnce(testing::Return(Fake())); + + EXPECT_CALL(*env_, CallObjectMethodV(Fake(3), Fake(1), _)) + .WillOnce(testing::Return(Fake(2))); + + // Make sure we try to get the method with the loaded class, not the direct + // object class. + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()V"))) + .WillOnce(testing::Return(Fake(1))); + + // Code under test. + jni::JvmRef jvm_ref{jvm_.get(), + kDefaultConfiguration}; + jni::LocalObject + obj1{AdoptLocal{}, Fake(1)}; + obj1("Foo"); + + this->TearDown(); +} + +} // namespace diff --git a/implementation/legacy/field_ref_test.cc b/implementation/legacy/field_ref_test.cc new file mode 100644 index 00000000..ac334809 --- /dev/null +++ b/implementation/legacy/field_ref_test.cc @@ -0,0 +1,170 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::GlobalObject; +using ::jni::LocalObject; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +// clang-format off +static constexpr Class java_class_under_test{ + "com/google/TestClass", + Field{"booleanField", jboolean{}}, + Field{"byteField", jbyte{}}, + Field{"charField", jchar{}}, + Field{"shortField", jshort{}}, + Field{"intField", jint{}}, + Field{"longField", jlong{}}, + Field{"floatField", jfloat{}}, + Field{"doubleField", jdouble{}} +}; +// clang-format on + +TEST_F(JniTest, Field_SimpleGetAndSet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("SomeField"), StrEq("I"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetIntField(_, Fake())).WillOnce(Return(999)); + EXPECT_CALL(*env_, SetIntField(_, Fake(), 123)); + + static constexpr Class java_class_under_test{"com/google/TestClass", + Field("SomeField", jint{})}; + + GlobalObject obj{}; + EXPECT_EQ(999, obj["SomeField"].Get()); + obj["SomeField"].Set(123); +} + +TEST_F(JniTest, Field_BooleanField) { + EXPECT_CALL(*env_, GetBooleanField); + EXPECT_CALL(*env_, SetBooleanField); + + LocalObject obj{}; + obj["booleanField"].Get(); + obj["booleanField"].Set(true); +} + +TEST_F(JniTest, Field_ByteField) { + EXPECT_CALL(*env_, GetByteField); + EXPECT_CALL(*env_, SetByteField); + + LocalObject obj{}; + obj["byteField"].Get(); + obj["byteField"].Set(jbyte{123}); +} + +TEST_F(JniTest, Field_CharField) { + EXPECT_CALL(*env_, GetCharField); + EXPECT_CALL(*env_, SetCharField); + + LocalObject obj{}; + obj["charField"].Get(); + obj["charField"].Set(jchar{'a'}); +} + +TEST_F(JniTest, Field_ShortField) { + EXPECT_CALL(*env_, GetShortField); + EXPECT_CALL(*env_, SetShortField); + + LocalObject obj{}; + obj["shortField"].Get(); + obj["shortField"].Set(jshort{123}); +} + +TEST_F(JniTest, Field_intField) { + EXPECT_CALL(*env_, GetIntField); + EXPECT_CALL(*env_, SetIntField); + + LocalObject obj{}; + obj["intField"].Get(); + obj["intField"].Set(123); +} + +TEST_F(JniTest, Field_longField) { + EXPECT_CALL(*env_, GetLongField); + EXPECT_CALL(*env_, SetLongField); + + LocalObject obj{}; + obj["longField"].Get(); + obj["longField"].Set(123); +} + +TEST_F(JniTest, Field_floatField) { + LocalObject obj{}; + EXPECT_CALL(*env_, GetFloatField); + EXPECT_CALL(*env_, SetFloatField); + obj["floatField"].Get(); + obj["floatField"].Set(123.f); +} + +TEST_F(JniTest, Field_doubleField) { + LocalObject obj{}; + EXPECT_CALL(*env_, GetDoubleField); + EXPECT_CALL(*env_, SetDoubleField); + obj["doubleField"].Get(); + obj["doubleField"].Set(123.); +} + +TEST_F(JniTest, Field_ObjectGet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(_, Fake())) + .WillOnce(Return(Fake())); + + static constexpr Class kClass{"com/google/TestClass", + Field{"classField", Class{"kClass2"}}}; + static constexpr Class kClass2{"kClass2"}; + + LocalObject obj{}; + LocalObject obj2 = obj["classField"].Get(); +} + +TEST_F(JniTest, Field_ObjectSet) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(_, Fake(), Fake())) + .Times(4); + + static constexpr Class kClass2{"kClass2"}; + static constexpr Class kClass{ + "com/google/TestClass", + // Field{"classField", Class{"kClass2"}}}; // also works + Field{"classField", kClass2}}; + + LocalObject obj{}; + LocalObject some_obj{AdoptLocal{}, Fake()}; + obj["classField"].Set(Fake()); + obj["classField"].Set(LocalObject{AdoptLocal{}, Fake()}); + obj["classField"].Set(some_obj); + obj["classField"].Set(std::move(some_obj)); +} + +} // namespace diff --git a/implementation/legacy/global_object_test.cc b/implementation/legacy/global_object_test.cc new file mode 100644 index 00000000..9589caa3 --- /dev/null +++ b/implementation/legacy/global_object_test.cc @@ -0,0 +1,285 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Constructor; +using ::jni::Fake; +using ::jni::Field; +using ::jni::GlobalObject; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::NewRef; +using ::jni::Params; +using ::jni::PromoteToGlobal; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::StrEq; + +TEST_F(JniTest, GlobalObject_AllowsNullPtrT) { + static constexpr Class kClass{"kClass"}; + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, NewGlobalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteGlobalRef).Times(0); + + GlobalObject obj{nullptr}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, GlobalObject_CallsNewAndDeleteOnNewObject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake())); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake()))); + + GlobalObject global_object{}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake())); +} + +TEST_F(JniTest, GlobalObject_ConstructsFromNonStandardConstructor) { + static constexpr Class kClass{ + "kClass", + Constructor{jfloat{}, jfloat{}}, + }; + + EXPECT_CALL(*env_, NewObjectV).WillOnce(Return(Fake())); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake()))); + + GlobalObject global_object{1.f, 2.f}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake())); +} + +TEST_F(JniTest, GlobalObject_ComparesAgainstOtherGlobalObjects) { + static constexpr Class kClass{ + "kClass", + Constructor{jfloat{}, jfloat{}}, + }; + static constexpr Class kClass2{ + "kClass2", + }; + GlobalObject val_1{AdoptGlobal{}, Fake(1)}; + GlobalObject val_2{AdoptGlobal{}, Fake(2)}; + + EXPECT_TRUE(val_1 == val_1); + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + EXPECT_TRUE(val_2 == val_2); + EXPECT_TRUE(val_2 != val_1); + EXPECT_FALSE(val_1 == val_2); +} + +TEST_F(JniTest, GlobalObject_DoesNotDeleteAnyLocalsForAdoptedGlobalJobject) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake())); + + GlobalObject global_object{AdoptGlobal{}, Fake()}; + + EXPECT_EQ(jobject{global_object}, Fake()); +} + +TEST_F(JniTest, GlobalObject_PromotesJobjectsOnConstruction) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake()))); + + static constexpr Class kClass{"kClass"}; + GlobalObject global_object{PromoteToGlobal{}, Fake()}; + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake())); +} + +TEST_F(JniTest, GlobalObject_PromotesDecoratedLocals) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake()))); + + static constexpr Class kClass{"kClass"}; + LocalObject local_obj{AdoptLocal{}, Fake()}; + // GlobalObject global_object{local_obj}; // doesn't compile (good). + GlobalObject global_object{std::move(local_obj)}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake())); +} + +// Identical to above but Local constructed in place. +TEST_F(JniTest, GlobalObject_PromotesDecoratedLocalsFromXValue) { + EXPECT_CALL(*env_, NewObjectV).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(AsGlobal(Fake()))); + + static constexpr Class kClass{"kClass"}; + GlobalObject global_object{ + LocalObject{AdoptLocal{}, Fake()}}; + + EXPECT_EQ(jobject{global_object}, AsGlobal(Fake())); +} + +TEST_F(JniTest, GlobalObject_CallsOnlyDeleteOnWrapCtor) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake())); + + static constexpr Class kClass{"com/google/CallsOnlyDeleteOnWrapCtor"}; + GlobalObject global_object{AdoptGlobal{}, Fake()}; + + EXPECT_NE(jobject{global_object}, nullptr); +} + +TEST_F(JniTest, GlobalObject_CallsNewGlobalRefOnCopy) { + static constexpr Class kClass{"kClass"}; + + EXPECT_CALL(*env_, NewGlobalRef(Fake(1))) + .WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))).Times(0); + + GlobalObject global_object{NewRef{}, Fake(1)}; + EXPECT_EQ(jobject{global_object}, Fake(2)); +} + +TEST_F(JniTest, GlobalObject_CallsDeleteOnceAfterAMoveConstruction) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake())); + + static constexpr Class kClass{ + "com/google/CallsDeleteOnceAfterAMoveConstruction"}; + GlobalObject global_object_1{AdoptGlobal{}, Fake()}; + EXPECT_EQ(jobject{global_object_1}, Fake()); + GlobalObject global_object_2{std::move(global_object_1)}; + + EXPECT_EQ(jobject{global_object_1}, nullptr); // NOLINT + EXPECT_EQ(jobject{global_object_2}, Fake()); +} + +TEST_F(JniTest, GlobalObject_FunctionsProperlyInSTLContainer) { + EXPECT_CALL(*env_, DeleteGlobalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake(2))); + + static constexpr Class kClass{ + "com/google/CallsDeleteOnceAfterAMoveConstruction"}; + GlobalObject global_object_1{AdoptGlobal{}, Fake(1)}; + GlobalObject global_object_2{AdoptGlobal{}, Fake(2)}; + std::tuple t{std::move(global_object_1), std::move(global_object_2)}; +} + +TEST_F(JniTest, GlobalObject_ValuesWorkAfterMoveConstructor) { + EXPECT_CALL(*env_, CallIntMethodV).Times(3); + EXPECT_CALL(*env_, SetIntField).Times(4); + + static constexpr Class kClass{ + "com/google/ValuesWorkAfterMoveConstructor", + Method{"Foo", jni::Return{}, Params{}}, + Field{"BarField", jint{}}}; + GlobalObject global_object_1{AdoptGlobal{}, Fake(1)}; + global_object_1("Foo", 1); + global_object_1("Foo", 2); + global_object_1["BarField"].Set(1); + + GlobalObject global_object_2{std::move(global_object_1)}; + global_object_2("Foo", 3); + global_object_2["BarField"].Set(2); + global_object_2["BarField"].Set(3); + global_object_2["BarField"].Set(4); + + GlobalObject global_object_3{AdoptGlobal{}, Fake(1)}; +} + +TEST_F(JniTest, GlobalObject_ObjectReturnsInstanceMethods) { + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), StrEq("()V"))) + .WillOnce(Return(Fake(1))); + EXPECT_CALL(*env_, NewObjectV(_, Fake(1), _)) + .WillOnce(Return(Fake())); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I"))) + .WillOnce(Return(Fake(2))); + EXPECT_CALL(*env_, CallIntMethodV(_, _, _)); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(Return(Fake(2))); + EXPECT_CALL(*env_, CallVoidMethodV(_, _, _)); + + EXPECT_CALL(*env_, + GetMethodID(_, + StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH" + "ardForTemplatesToHandle"), + StrEq("(IFIFD)D"))); + EXPECT_CALL(*env_, CallDoubleMethodV(_, _, _)); + + static constexpr Class java_class_under_test{ + "com/google/ObjectReturnsInstanceMethods", + Method{"Foo", jni::Return{}, Params{}}, + Method{"Baz", jni::Return{}, Params{}}, + Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates" + "ToHandle", + jni::Return{}, + Params{}}}; + + GlobalObject global_object{}; + global_object("Foo", 1); + global_object("Baz", 2.f); + global_object( + "AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", + int{}, float{}, int{}, float{}, double{}); +} + +TEST_F(JniTest, GlobalObject_ReleasesGlobalsForAlternateConstructors) { + static constexpr Class java_class_under_test{ + "ReleasesGlobalsForAlternateConstructors", jni::Constructor{}}; + GlobalObject g1{1}; + GlobalObject g2{2}; + GlobalObject g3{3}; + EXPECT_CALL(*env_, DeleteGlobalRef(_)).Times(3); +} + +TEST_F(JniTest, GlobalObject_SupportsPassingAPrvalue) { + static constexpr Class kTestClass1{"TestClass1"}; + static constexpr Class kTestClass2{ + "TestClass2", Method{"Foo", jni::Return{}, jni::Params{kTestClass1}}}; + + GlobalObject a{}; + GlobalObject b{}; + b("Foo", std::move(a)); +} + +TEST_F(JniTest, GlobalObjects_PromoteRValuesFromEmittedLValues) { + static constexpr Class kClass1{"TestClass1"}; + static constexpr Class kClass2{ + "TestClass2", Method{"Foo", jni::Return{kClass1}, jni::Params{}}}; + + LocalObject b{}; + GlobalObject a{b("Foo")}; + + a = b("Foo"); +} + +} // namespace diff --git a/implementation/legacy/local_array_field_multidimensional_test.cc b/implementation/legacy/local_array_field_multidimensional_test.cc new file mode 100644 index 00000000..19e327f9 --- /dev/null +++ b/implementation/legacy/local_array_field_multidimensional_test.cc @@ -0,0 +1,152 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::Rank; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +static constexpr Class kRank2{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}, Rank<2>{}}}, + Field{"ByteArray", Array{jbyte{}, Rank<2>{}}}, + Field{"CharArray", Array{jchar{}, Rank<2>{}}}, + Field{"ShortArray", Array{jshort{}, Rank<2>{}}}, + Field{"IntArray", Array{jint{}, Rank<2>{}}}, + Field{"FloatArray", Array{jfloat{}, Rank<2>{}}}, + Field{"DoubleArray", Array{jdouble{}, Rank<2>{}}}, + Field{"LongArray", Array{jlong{}, Rank<2>{}}}, + Field{"ObjectArray", Array{kClass, Rank<2>{}}}, +}; + +TEST_F(JniTest, LocalArrayField_Rank2) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[J"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[LkClass;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillOnce(::testing::Return(Fake(1))) + .WillOnce(::testing::Return(Fake(2))) + .WillOnce(::testing::Return(Fake(3))) + .WillOnce(::testing::Return(Fake(4))) + .WillOnce(::testing::Return(Fake(5))) + .WillOnce(::testing::Return(Fake(6))) + .WillOnce(::testing::Return(Fake(7))) + .WillOnce(::testing::Return(Fake(8))) + .WillOnce(::testing::Return(Fake(9))); + + LocalObject obj{AdoptLocal{}, Fake()}; + + EXPECT_EQ(static_cast(obj["BooleanArray"].Get()), + Fake(1)); + EXPECT_EQ(static_cast(obj["ByteArray"].Get()), + Fake(2)); + EXPECT_EQ(static_cast(obj["CharArray"].Get()), + Fake(3)); + EXPECT_EQ(static_cast(obj["ShortArray"].Get()), + Fake(4)); + EXPECT_EQ(static_cast(obj["IntArray"].Get()), + Fake(5)); + EXPECT_EQ(static_cast(obj["FloatArray"].Get()), + Fake(6)); + EXPECT_EQ(static_cast(obj["DoubleArray"].Get()), + Fake(7)); + EXPECT_EQ(static_cast(obj["LongArray"].Get()), + Fake(8)); + EXPECT_EQ(static_cast(obj["ObjectArray"].Get()), + Fake(9)); +} + +static constexpr Class kRank3{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}, Rank<3>{}}}, + Field{"ByteArray", Array{jbyte{}, Rank<3>{}}}, + Field{"CharArray", Array{jchar{}, Rank<3>{}}}, + Field{"ShortArray", Array{jshort{}, Rank<3>{}}}, + Field{"IntArray", Array{jint{}, Rank<3>{}}}, + Field{"FloatArray", Array{jfloat{}, Rank<3>{}}}, + Field{"DoubleArray", Array{jdouble{}, Rank<3>{}}}, + Field{"LongArray", Array{jlong{}, Rank<3>{}}}, + Field{"ObjectArray", Array{kClass, Rank<3>{}}}, +}; + +TEST_F(JniTest, LocalArrayField_Rank3) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[[[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[[[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[[[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[[[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[[[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[[[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[[[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[[[J"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ObjectArray"), StrEq("[[[LkClass;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillOnce(::testing::Return(Fake(1))) + .WillOnce(::testing::Return(Fake(2))) + .WillOnce(::testing::Return(Fake(3))) + .WillOnce(::testing::Return(Fake(4))) + .WillOnce(::testing::Return(Fake(5))) + .WillOnce(::testing::Return(Fake(6))) + .WillOnce(::testing::Return(Fake(7))) + .WillOnce(::testing::Return(Fake(8))) + .WillOnce(::testing::Return(Fake(9))); + + LocalObject obj{AdoptLocal{}, Fake()}; + + EXPECT_EQ(static_cast(obj["BooleanArray"].Get()), + Fake(1)); + EXPECT_EQ(static_cast(obj["ByteArray"].Get()), + Fake(2)); + EXPECT_EQ(static_cast(obj["CharArray"].Get()), + Fake(3)); + EXPECT_EQ(static_cast(obj["ShortArray"].Get()), + Fake(4)); + EXPECT_EQ(static_cast(obj["IntArray"].Get()), + Fake(5)); + EXPECT_EQ(static_cast(obj["FloatArray"].Get()), + Fake(6)); + EXPECT_EQ(static_cast(obj["DoubleArray"].Get()), + Fake(7)); + EXPECT_EQ(static_cast(obj["LongArray"].Get()), + Fake(8)); + EXPECT_EQ(static_cast(obj["ObjectArray"].Get()), + Fake(9)); +} + +} // namespace diff --git a/implementation/legacy/local_array_field_test.cc b/implementation/legacy/local_array_field_test.cc new file mode 100644 index 00000000..06839452 --- /dev/null +++ b/implementation/legacy/local_array_field_test.cc @@ -0,0 +1,480 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Rank; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// Fields: Smoke Tests. +//////////////////////////////////////////////////////////////////////////////// +static constexpr Class kFieldClass{ + "kFieldClass", + Field{"BooleanArray", Array{jboolean{}}}, + Field{"ByteArray", Array{jbyte{}}}, + Field{"CharArray", Array{jchar{}}}, + Field{"ShortArray", Array{jshort{}}}, + Field{"IntArray", Array{jint{}}}, + Field{"FloatArray", Array{jfloat{}}}, + Field{"DoubleArray", Array{jdouble{}}}, + Field{"LongArray", Array{jlong{}}}, + Field{"ObjectArrayRank1", Array{kClass2}}, + Field{"ObjectArrayRank2", Array{kClass2, Rank<2>{}}}, + Field{"ObjectArrayRank3", Array{kClass2, Rank<3>{}}}, +}; + +TEST_F(JniTest, ArrayField_Object_Iteration_Rank_1) { + EXPECT_CALL(*env_, GetObjectField) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(100))) + .WillOnce(::testing::Return(Fake(101))) + .WillOnce(::testing::Return(Fake(102))); + + int i = 100; + LocalObject obj{Fake()}; + for (LocalObject a : obj["ObjectArrayRank1"].Get().Pin()) { + EXPECT_EQ(static_cast(a), Fake(i)); + i++; + } +} + +TEST_F(JniTest, ArrayField_Object_Iteration_Rank_2) { + EXPECT_CALL(*env_, GetObjectField) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(100))) + .WillOnce(::testing::Return(Fake(101))) + .WillOnce(::testing::Return(Fake(102))); + + int i = 100; + LocalObject obj{Fake(1)}; + for (LocalArray a : + obj["ObjectArrayRank2"].Get().Pin()) { + EXPECT_EQ(static_cast(a), Fake(i)); + i++; + } +} + +TEST_F(JniTest, Array_FieldTests) { + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank2"), StrEq("[[LkClass2;"))); + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank3"), StrEq("[[[LkClass2;"))); + + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake())) + .WillOnce(::testing::Return(Fake(1))) + .WillOnce(::testing::Return(Fake(2))) + .WillOnce(::testing::Return(Fake(3))); + + LocalObject obj{AdoptLocal{}, Fake()}; + + EXPECT_EQ(static_cast(obj["BooleanArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["ByteArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["CharArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["ShortArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["IntArray"].Get()), Fake()); + EXPECT_EQ(static_cast(obj["FloatArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["DoubleArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["LongArray"].Get()), + Fake()); + EXPECT_EQ(static_cast(obj["ObjectArrayRank1"].Get()), + Fake(1)); + EXPECT_EQ(static_cast(obj["ObjectArrayRank2"].Get()), + Fake(2)); + EXPECT_EQ(static_cast(obj["ObjectArrayRank3"].Get()), + Fake(3)); +} + +TEST_F(JniTest, Array_Field_Boolean_Test) { + std::unique_ptr fake_storage_ptr(new jboolean()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("BooleanArray"), StrEq("[Z"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetBooleanArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseBooleanArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseBooleanArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["BooleanArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["BooleanArray"].Set(Fake()); + obj["BooleanArray"].Set( + LocalArray{AdoptLocal{}, Fake()}); + obj["BooleanArray"].Set(arr2); + obj["BooleanArray"].Set(obj["BooleanArray"].Get()); + + EXPECT_EQ(obj["BooleanArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["BooleanArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Byte_Test) { + std::unique_ptr fake_storage_ptr(new jbyte()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ByteArray"), StrEq("[B"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetByteArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseByteArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseByteArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["ByteArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["ByteArray"].Set(Fake()); + obj["ByteArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["ByteArray"].Set(arr2); + obj["ByteArray"].Set(obj["ByteArray"].Get()); + EXPECT_EQ(obj["ByteArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["ByteArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Char_Test) { + std::unique_ptr fake_storage_ptr(new jchar()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("CharArray"), StrEq("[C"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetCharArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseCharArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseCharArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["CharArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["CharArray"].Set(Fake()); + obj["CharArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["CharArray"].Set(arr2); + obj["CharArray"].Set(obj["CharArray"].Get()); + EXPECT_EQ(obj["CharArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["CharArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Short_Test) { + std::unique_ptr fake_storage_ptr(new jshort()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("ShortArray"), StrEq("[S"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetShortArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseShortArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseShortArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["ShortArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["ShortArray"].Set(Fake()); + obj["ShortArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["ShortArray"].Set(arr2); + obj["ShortArray"].Set(obj["ShortArray"].Get()); + EXPECT_EQ(obj["ShortArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["ShortArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Int_Test) { + std::unique_ptr fake_storage_ptr(new jint()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("IntArray"), StrEq("[I"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseIntArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, ReleaseIntArrayElements( + Fake(), fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["IntArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["IntArray"].Set(Fake()); + obj["IntArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["IntArray"].Set(arr2); + obj["IntArray"].Set(obj["IntArray"].Get()); + EXPECT_EQ(obj["IntArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["IntArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Float_Test) { + std::unique_ptr fake_storage_ptr(new jfloat()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("FloatArray"), StrEq("[F"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetFloatArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseFloatArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseFloatArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["FloatArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["FloatArray"].Set(Fake()); + obj["FloatArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["FloatArray"].Set(arr2); + obj["FloatArray"].Set(obj["FloatArray"].Get()); + EXPECT_EQ(obj["FloatArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["FloatArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Double_Test) { + std::unique_ptr fake_storage_ptr(new jdouble()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("DoubleArray"), StrEq("[D"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetDoubleArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseDoubleArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseDoubleArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["DoubleArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["DoubleArray"].Set(Fake()); + obj["DoubleArray"].Set( + LocalArray{AdoptLocal{}, Fake()}); + obj["DoubleArray"].Set(arr2); + obj["DoubleArray"].Set(obj["DoubleArray"].Get()); + EXPECT_EQ(obj["DoubleArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["DoubleArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Long_Test) { + std::unique_ptr fake_storage_ptr(new jlong()); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("LongArray"), StrEq("[J"))) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetLongArrayElements) + .WillRepeatedly(::testing::Return(fake_storage_ptr.get())); + EXPECT_CALL(*env_, ReleaseLongArrayElements(Fake(), + fake_storage_ptr.get(), 0)); + EXPECT_CALL(*env_, + ReleaseLongArrayElements(Fake(), + fake_storage_ptr.get(), JNI_ABORT)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr{obj["LongArray"].Get()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + obj["LongArray"].Set(Fake()); + obj["LongArray"].Set(LocalArray{AdoptLocal{}, Fake()}); + obj["LongArray"].Set(arr2); + obj["LongArray"].Set(obj["LongArray"].Get()); + EXPECT_EQ(obj["LongArray"].Get().Pin().ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj["LongArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); +} + +TEST_F(JniTest, Array_Field_Object_Test) { + EXPECT_CALL(*env_, + GetFieldID(_, StrEq("ObjectArrayRank1"), StrEq("[LkClass2;"))) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetObjectField(Fake(), Fake())) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, SetObjectField(Fake(), Fake(), + Fake())) + .Times(4); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake(), 2)); + + LocalObject obj{AdoptLocal{}, Fake()}; + LocalArray arr2{AdoptLocal{}, Fake()}; + LocalArray arr{obj["ObjectArrayRank1"].Get()}; + obj["ObjectArrayRank1"].Set(Fake()); + obj["ObjectArrayRank1"].Set( + LocalArray{AdoptLocal{}, Fake()}); + obj["ObjectArrayRank1"].Set(arr2); + obj["ObjectArrayRank1"].Set(obj["ObjectArrayRank1"].Get()); + obj["ObjectArrayRank1"].Get().Get(2); +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_1) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "ArrayMultiTest", + Field{"Foo", Array{kClass2}}, + }; + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;"))); + + LocalObject obj{Fake()}; + LocalArray{obj["Foo"].Get()}; +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "kClass", + Field{"Foo", Array{kClass2, Rank<2>{}}}, + }; + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;"))); + + LocalObject obj{Fake()}; + LocalArray arr_from_field{obj["Foo"].Get()}; +} + +TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2_Iteration) { + static constexpr Class kClass2{"kClass2"}; + + static constexpr Class kClass{ + "kClass", + Field{"Foo", Array{kClass2, Rank<2>{}}}, + }; + + InSequence seq; + + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;"))); + EXPECT_CALL(*env_, GetObjectField) + .WillRepeatedly(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(::testing::Return(3)); + + // A temporary local array is materialised in the field access. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + // Internal elements looped over. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(100))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(100))); + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(101))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(101))); + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(::testing::Return(Fake(102))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(102))); + + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + + // Object with queried array field. + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + + LocalObject obj{AdoptLocal{}, Fake(1)}; + int i = 100; + for (LocalArray obj : obj["Foo"].Get().Pin()) { + EXPECT_EQ(static_cast(obj), Fake(i)); + i++; + } +} + +} // namespace diff --git a/implementation/legacy/local_array_iteration_test.cc b/implementation/legacy/local_array_iteration_test.cc new file mode 100644 index 00000000..3432e8ed --- /dev/null +++ b/implementation/legacy/local_array_iteration_test.cc @@ -0,0 +1,262 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::ArrayView; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Rank 0. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, IteratesOver1DRange) { + std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(Return(expected.data())); + + LocalArray new_array{AdoptLocal{}, Fake()}; + + int i{0}; + for (jint val : new_array.Pin()) { + EXPECT_EQ(val, expected[i]); + ++i; + } + EXPECT_EQ(i, 10); +} + +TEST_F(JniTest, WorksWithSTLComparison) { + std::array expected{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(10)); + EXPECT_CALL(*env_, GetIntArrayElements) + .WillRepeatedly(Return(expected.data())); + + LocalArray new_array{AdoptLocal{}, Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, WorksWithSTLComparisonOfObjects) { + std::array expected{Fake(1), Fake(2), Fake(3)}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))); + + LocalArray new_array{Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, WorksWithSTLComparisonOfRichlyDecoratedObjects) { + std::array expected{LocalObject{AdoptLocal{}, Fake(1)}, + LocalObject{AdoptLocal{}, Fake(2)}, + LocalObject{AdoptLocal{}, Fake(3)}}; + + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(3)); + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))); + + LocalArray new_array{AdoptLocal{}, Fake()}; + ArrayView array_view = new_array.Pin(); + EXPECT_TRUE( + std::equal(array_view.begin(), array_view.end(), expected.begin())); +} + +TEST_F(JniTest, 2D_Iterates) { + int a[5] = {1, 2, 3, 4, 5}; + std::array expected{1, 2, 3, 4, 5}; + + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake())); + EXPECT_CALL(*env_, GetArrayLength) + .WillOnce(Return(5)) // outer + .WillOnce(Return(1)) // inner: 1, running sum: 1 + .WillOnce(Return(2)) // inner: 1, running sum: 3 + .WillOnce(Return(3)) // inner: 1, running sum: 6 + .WillOnce(Return(4)) // inner: 1, running sum: 10 + .WillOnce(Return(5)); // inner: 1, running sum: 15 + + // 5: Once per outer array element. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray new_array{5, Fake()}; + + int sum = 0; + for (LocalArray arr_1d : new_array.Pin()) { + // Note: Each `Pin` below triggers a separate `GetIntArrayElements`. + { + auto pinn = arr_1d.Pin(); + EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin())); + } + + // Note: GetArrayLength is not called again (it's cached). + { + for (jint val : arr_1d.Pin()) { + sum += val; + } + } + } + + // 1 + 3 + 6 + 10 + 15 = 35 + EXPECT_EQ(sum, 35); +} + +// Identical to above except with raw loops. +TEST_F(JniTest, 2D_Iterates_Raw_loops) { + int a[5] = {1, 2, 3, 4, 5}; + std::array expected{1, 2, 3, 4, 5}; + + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake())); + EXPECT_CALL(*env_, GetArrayLength) + .WillOnce(Return(5)) // outer + .WillOnce(Return(1)) // inner: 1, running sum: 1 + .WillOnce(Return(2)) // inner: 1, running sum: 3 + .WillOnce(Return(3)) // inner: 1, running sum: 6 + .WillOnce(Return(4)) // inner: 1, running sum: 10 + .WillOnce(Return(5)); // inner: 1, running sum: 15 + + // 5: Once per outer array element. + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned intArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + // 5 (outer array length) * 2 (pins per) = 10 + EXPECT_CALL(*env_, GetIntArrayElements).Times(10).WillRepeatedly(Return(a)); + EXPECT_CALL(*env_, ReleaseIntArrayElements).Times(10); + + LocalArray new_array{5, Fake()}; + + int sum = 0; + int loop_size = new_array.Length(); + for (int i = 0; i < loop_size; ++i) { + LocalArray arr_1d{new_array.Get(i)}; + + // Note: Each `Pin` below triggers a separate `GetIntArrayElements`. + { + auto pinn = arr_1d.Pin(); + EXPECT_TRUE(std::equal(pinn.begin(), pinn.end(), expected.begin())); + } + + // Note: GetArrayLength is not called again (it's cached). + { + for (jint val : arr_1d.Pin()) { + sum += val; + } + } + } + + // 1 + 3 + 6 + 10 + 15 = 35 + EXPECT_EQ(sum, 35); +} + +// Identical to above except with object loops. +TEST_F(JniTest, 2D_Iterates_Raw_loops_of_Objects) { + EXPECT_CALL(*env_, FindClass(StrEq("[LkClass;"))); + EXPECT_CALL(*env_, NewObjectArray(5, _, Fake(100))); + EXPECT_CALL(*env_, GetArrayLength).WillOnce(Return(5)); // outer + + EXPECT_CALL(*env_, GetObjectArrayElement) + .WillOnce(Return(Fake(1))) + .WillOnce(Return(Fake(2))) + .WillOnce(Return(Fake(3))) + .WillOnce(Return(Fake(4))) + .WillOnce(Return(Fake(5))); + + // All the returned objectArrays are deleted. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(3))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(4))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(5))); + + // Note: This is just 0 (default), not 100. 100 is the sample (i.e. template) + // object, no arg is the default that is created. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + + LocalArray new_array{5, Fake(100)}; + EXPECT_EQ(jobject{new_array}, Fake()); + + int i = 1; + for (LocalArray arr : new_array.Pin()) { + EXPECT_EQ(jobject(arr), Fake(i)); + ++i; + } +} + +} // namespace diff --git a/implementation/legacy/local_array_method_multidimensional_test.cc b/implementation/legacy/local_array_method_multidimensional_test.cc new file mode 100644 index 00000000..1e1a9719 --- /dev/null +++ b/implementation/legacy/local_array_method_multidimensional_test.cc @@ -0,0 +1,119 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, V_2I) { + static constexpr Class kClass{"kClass", + Method{"I", jni::Return{Array{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I"))); + + LocalObject obj{Fake()}; + obj("I"); +} + +TEST_F(JniTest, V_2LkClass) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{Array{kClass2, Rank<2>{}}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;"))); + + LocalObject obj{Fake()}; + obj("Foo"); +} + +//////////////////////////////////////////////////////////////////////////////// +// Complex: Arrays in Params & Return. +//////////////////////////////////////////////////////////////////////////////// +static constexpr Class kArrClass{ + "ArrClass", + Method{"Foo", jni::Return{Array{jint{}}}, Params{Array{}}}, + Method{"Baz", jni::Return{Array{kClass2}}, Params{Array{}}}, + Method{"Bar", jni::Return{Array{Class{"kClass3"}, Rank<2>{}}}, + Params{Array{}, Array{double{}}}}, +}; + +TEST_F(JniTest, 3I1D_2LkClass) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); + + LocalObject obj{Fake()}; + obj("Bar", Fake(), Fake()); +} + +TEST_F(JniTest, 2I_I) { + static constexpr Class kClass{ + "kClass", Method{"I", jni::Return{}, Params{Array{}}}}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("([[I)I"))); + + LocalObject obj{Fake()}; + // obj("I", jintArray{nullptr}); // doesn't compile (good). + obj("I", Fake()); + obj("I", LocalArray{Fake()}); +} + +TEST_F(JniTest, 3I_1LkClass) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("([[[I)[LkClass2;"))); + + LocalObject obj{Fake()}; + + // The compiler complains of unused variable here. This would be worth digging + // into to understand the underlying cause (i.e. only the following fails). + // LocalArray ret = obj("Baz", jobjectArray{nullptr}); + + // TODO(b/143908983): CTAD is failing. + // LocalArray ret = obj("Baz", jobjectArray{nullptr}); + + LocalArray ret = obj("Baz", Fake()); + static_assert(std::is_same_v>); +} + +TEST_F(JniTest, 3I1D_1LkClass) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); + + LocalObject obj{Fake()}; + obj("Bar", Fake(), Fake()); +} + +} // namespace diff --git a/implementation/legacy/local_array_method_test.cc b/implementation/legacy/local_array_method_test.cc new file mode 100644 index 00000000..afb7f4c7 --- /dev/null +++ b/implementation/legacy/local_array_method_test.cc @@ -0,0 +1,228 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass2{"kClass2"}; + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ReturnSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{Array{jboolean{}}}}, + Method{"ByteArray", Return{Array{jbyte{}}}}, + Method{"CharArray", Return{Array{jchar{}}}}, + Method{"ShortArray", Return{Array{jshort{}}}}, + Method{"IntArray", Return{Array{jint{}}}}, + Method{"FloatArray", Return{Array{jfloat{}}}}, + Method{"DoubleArray", Return{Array{jdouble{}}}}, + Method{"LongArray", Return{Array{jlong{}}}}, + Method{"ObjectArray", Return{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, CallObjectMethodV) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())); + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("()[Z"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("()[B"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("()[C"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("()[S"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("()[I"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("()[J"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("()[F"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("()[D"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); + + LocalArray bool_array{obj("BooleanArray")}; + EXPECT_EQ((static_cast(bool_array)), (Fake())); + + LocalArray byte_array{obj("ByteArray")}; + EXPECT_EQ((static_cast(byte_array)), (Fake())); + + LocalArray char_array{obj("CharArray")}; + EXPECT_EQ((static_cast(char_array)), (Fake())); + + LocalArray short_array{obj("ShortArray")}; + EXPECT_EQ((static_cast(short_array)), (Fake())); + + LocalArray int_array{obj("IntArray")}; + EXPECT_EQ((static_cast(int_array)), (Fake())); + + LocalArray long_array{obj("LongArray")}; + EXPECT_EQ((static_cast(long_array)), (Fake())); + + LocalArray float_array{obj("FloatArray")}; + EXPECT_EQ((static_cast(float_array)), (Fake())); + + LocalArray double_array{obj("DoubleArray")}; + EXPECT_EQ((static_cast(double_array)), (Fake())); + + LocalArray object_array{obj("ObjectArray")}; + EXPECT_EQ((static_cast(object_array)), (Fake())); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Params. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ParamsSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{}, Params{Array{jboolean{}}}}, + Method{"ByteArray", Return{}, Params{Array{jbyte{}}}}, + Method{"CharArray", Return{}, Params{Array{jchar{}}}}, + Method{"ShortArray", Return{}, Params{Array{jshort{}}}}, + Method{"IntArray", Return{}, Params{Array{jint{}}}}, + Method{"FloatArray", Return{}, Params{Array{jfloat{}}}}, + Method{"DoubleArray", Return{}, Params{Array{jdouble{}}}}, + Method{"LongArray", Return{}, Params{Array{jlong{}}}}, + Method{"ObjectArray", Return{}, Params{Array{kClass2}}}, + }; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("ObjectArray"), StrEq("([LkClass2;)V"))); + + obj("BooleanArray", LocalArray{Fake()}); + obj("ByteArray", LocalArray{Fake()}); + obj("CharArray", LocalArray{Fake()}); + obj("ShortArray", LocalArray{Fake()}); + obj("IntArray", LocalArray{Fake()}); + obj("FloatArray", LocalArray{Fake()}); + obj("DoubleArray", LocalArray{Fake()}); + obj("LongArray", LocalArray{Fake()}); + obj("ObjectArray", LocalArray{Fake()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Complex. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, ComplexSmokeTest) { + static constexpr Class kClass{ + "kClass", + Method{"BooleanArray", Return{Array{jboolean{}}}, + Params{Array{jboolean{}}}}, + Method{"ByteArray", Return{Array{jbyte{}}}, Params{Array{jbyte{}}}}, + Method{"CharArray", Return{Array{jchar{}}}, Params{Array{jchar{}}}}, + Method{"ShortArray", Return{Array{jshort{}}}, Params{Array{jshort{}}}}, + Method{"IntArray", Return{Array{jint{}}}, Params{Array{jint{}}}}, + Method{"FloatArray", Return{Array{jfloat{}}}, Params{Array{jfloat{}}}}, + Method{"DoubleArray", Return{Array{jdouble{}}}, Params{Array{jdouble{}}}}, + Method{"LongArray", Return{Array{jlong{}}}, Params{Array{jlong{}}}}, + Method{"ObjectArray", Return{Array{kClass2}}, Params{Array{kClass2}}}, + }; + + EXPECT_CALL(*env_, CallObjectMethodV) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())) + .WillOnce(testing::Return(Fake())); + + LocalObject obj{Fake()}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("BooleanArray"), StrEq("([Z)[Z"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ByteArray"), StrEq("([B)[B"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("CharArray"), StrEq("([C)[C"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ShortArray"), StrEq("([S)[S"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("IntArray"), StrEq("([I)[I"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("FloatArray"), StrEq("([F)[F"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("DoubleArray"), StrEq("([D)[D"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("LongArray"), StrEq("([J)[J"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("ObjectArray"), + StrEq("([LkClass2;)[LkClass2;"))); + + LocalArray bool_array{ + obj("BooleanArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(bool_array)), (Fake())); + + LocalArray byte_array{ + obj("ByteArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(byte_array)), (Fake())); + + LocalArray char_array{ + obj("CharArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(char_array)), (Fake())); + + LocalArray short_array{ + obj("ShortArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(short_array)), (Fake())); + + LocalArray int_array{ + obj("IntArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(int_array)), (Fake())); + + LocalArray float_array{ + obj("FloatArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(float_array)), (Fake())); + + LocalArray double_array{ + obj("DoubleArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(double_array)), (Fake())); + + LocalArray long_array{ + obj("LongArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(long_array)), (Fake())); + + LocalArray object_array{obj( + "ObjectArray", LocalArray{Fake()})}; + EXPECT_EQ((static_cast(object_array)), (Fake())); +} + +} // namespace diff --git a/implementation/legacy/local_array_multidimensional_test.cc b/implementation/legacy/local_array_multidimensional_test.cc new file mode 100644 index 00000000..3336a7d6 --- /dev/null +++ b/implementation/legacy/local_array_multidimensional_test.cc @@ -0,0 +1,112 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalArray; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Construction. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_no_value) { + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake())); + + LocalArray{std::size_t{10}, Fake()}; +} + +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_xref) { + EXPECT_CALL(*env_, FindClass(StrEq("[I"))); + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake())); + + LocalArray{std::size_t{10}, + LocalArray{AdoptLocal{}, Fake()}}; +} + +TEST_F(JniTest, BuildsFromSizeForMultiDimensional_primitive_lvalue) { + EXPECT_CALL(*env_, NewObjectArray(10, _, Fake())); + + LocalArray arr{AdoptLocal{}, Fake()}; + LocalArray{std::size_t{10}, arr}; +} + +//////////////////////////////////////////////////////////////////////////////// +// Getters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, GetsIntValues) { + EXPECT_CALL(*env_, GetObjectArrayElement(Fake(), 0)) + .WillOnce(::testing::Return(Fake(0))); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake(), 1)) + .WillOnce(::testing::Return(Fake(1))); + EXPECT_CALL(*env_, GetObjectArrayElement(Fake(), 2)) + .WillOnce(::testing::Return(Fake(2))); + LocalArray arr{std::size_t{10}, Fake()}; + + EXPECT_EQ((static_cast(arr.Get(0))), (Fake(0))); + EXPECT_EQ((static_cast(arr.Get(1))), (Fake(1))); + EXPECT_EQ((static_cast(arr.Get(2))), (Fake(2))); +} + +//////////////////////////////////////////////////////////////////////////////// +// Setters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, SetsIntValues) { + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake(), 0, Fake())); + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake(), 1, Fake())); + EXPECT_CALL( + *env_, SetObjectArrayElement(Fake(), 2, Fake())); + + LocalArray array_arg{AdoptLocal{}, Fake()}; + LocalArray arr{std::size_t{10}, Fake()}; + arr.Set(0, array_arg); + arr.Set(1, array_arg); + arr.Set(2, std::move(array_arg)); +} + +TEST_F(JniTest, SetsObjectValues) { + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(1), 0, + Fake(2))); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(1), 1, + Fake(2))); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(1), 2, + Fake(2))); + + LocalArray array_arg{AdoptLocal{}, Fake(2)}; + LocalArray arr{AdoptLocal{}, Fake(1)}; + arr.Set(0, array_arg); + arr.Set(1, array_arg); + arr.Set(2, std::move(array_arg)); +} + +} // namespace diff --git a/implementation/legacy/local_array_string_test.cc b/implementation/legacy/local_array_string_test.cc new file mode 100644 index 00000000..a78a221b --- /dev/null +++ b/implementation/legacy/local_array_string_test.cc @@ -0,0 +1,204 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +using ::jni::Array; +using ::jni::ArrayStrip_t; +using ::jni::CDecl_t; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::kJavaLangString; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::RegularToArrayTypeMap_t; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +namespace { + +static constexpr Class kClass{"kClass"}; + +//////////////////////////////////////////////////////////////////////////////// +// Strings are unusual in that they have their own type (jstring) but are +// almost completely objects otherwise. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Construction / Materialisation. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_ConstructsFromAnotherStringArray) { + LocalArray arr_1{Fake()}; + LocalArray arr_2{std::move(arr_1)}; +} + +TEST_F(JniTest, Array_CorrectSignatureForStringParams) { + EXPECT_CALL(*env_, FindClass(StrEq("ClassThatReturnsArrays"))); + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("([Ljava/lang/String;)V"))); + + static constexpr Class kClass{ + "ClassThatReturnsArrays", + Method{"StringArray", jni::Return{}, jni::Params{Array{jstring{}}}}, + }; + + LocalObject obj{jobject{nullptr}}; + LocalArray arr{3}; + obj("StringArray", arr); +} + +//////////////////////////////////////////////////////////////////////////////// +// Caches Length. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CachesLengthExactlyOnceOnFirstRequest) { + EXPECT_CALL(*env_, GetArrayLength).Times(0); + + LocalArray obj{Fake()}; + + EXPECT_CALL(*env_, GetArrayLength).Times(1).WillOnce(Return(5)); + EXPECT_EQ(obj.Length(), 5); + EXPECT_EQ(obj.Length(), 5); + EXPECT_EQ(obj.Length(), 5); +} + +//////////////////////////////////////////////////////////////////////////////// +// Setters. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_LocalStringRValuesCanBeSet) { + EXPECT_CALL(*env_, DeleteLocalRef(_)) + .Times(1); // array (object is moved from) + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); + + LocalArray arr{3}; + arr.Set(0, LocalString{Fake()}); +} + +TEST_F(JniTest, Array_LocalVanillaObjectRValuesCanBeSet) { + // Unfortunately this is getting cached separately by `LocalArray`. + // In the future, this should drop to 1. + EXPECT_CALL(*env_, FindClass(StrEq("java/lang/String"))).Times(2); + + EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(2); // array, in place obj + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(2); // FindClass + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(0); + + LocalArray arr{ + 3, LocalObject{"Foo"}}; + arr.Set(0, LocalObject{Fake()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Return. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{Array{jstring{}}}}, + }; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("()[Ljava/lang/String;"))); + LocalArray arr = obj("StringArray"); +} + +//////////////////////////////////////////////////////////////////////////////// +// As Param. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectParamSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Method{"StringArray", jni::Return{}, Params{Array{jstring{}}}}, + }; + + LocalObject obj{Fake()}; + EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), + StrEq("([Ljava/lang/String;)V"))); + LocalArray arr{2}; + obj("StringArray", arr); +} + +TEST_F(JniTest, LocalStringArray_ConstructsObjectsForLValues) { + // Unlike POD, objects are constructed with a size, a jclass, and an init + // object. This makes for a slightly different API then other objects. + EXPECT_CALL(*env_, NewObjectArray); + + LocalObject default_object{}; + LocalArray local_object_array{5, default_object}; +} + +/* +// TODO(b/143908983): Restore after fixing ubsan failures. +TEST_F(JniTest, Array_StringsCanBeSetOnLocalString) { + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 0, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 1, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 2, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 3, _)); + EXPECT_CALL(*env_, SetObjectArrayElement(Fake(), 4, _)); + + const char* kFoo = "kFoo"; + const std::string kNar = "kNar"; + + LocalArray arr{5, LocalObject{"Foo"}}; + arr.Set(0, "Bar"); + arr.Set(1, std::string{"Baz"}); + arr.Set(2, std::string_view{"Bar"}); + arr.Set(3, std::string_view{kFoo}); + arr.Set(4, std::string_view{kNar}); +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +// String Fields. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, Array_CorrectFieldSignatureForStrings) { + static constexpr Class kClass{ + "kClass", + Field{"StringArrayRank1", Array{jstring{}}}, + Field{"StringArrayRank2", Array{jstring{}, Rank<2>{}}}, + Field{"StringArrayRank3", Array{jstring{}, Rank<3>{}}}, + }; + + LocalObject obj{jobject{nullptr}}; + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank1"), + StrEq("[Ljava/lang/String;"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank2"), + StrEq("[[Ljava/lang/String;"))); + EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank3"), + StrEq("[[[Ljava/lang/String;"))); + + LocalArray arr1 = obj["StringArrayRank1"].Get(); + LocalArray arr2 = obj["StringArrayRank2"].Get(); + LocalArray arr3 = obj["StringArrayRank3"].Get(); +} + +} // namespace diff --git a/implementation/legacy/local_object_test.cc b/implementation/legacy/local_object_test.cc new file mode 100644 index 00000000..0d71d462 --- /dev/null +++ b/implementation/legacy/local_object_test.cc @@ -0,0 +1,327 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::NewRef; +using ::jni::Params; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::InSequence; +using ::testing::StrEq; + +static constexpr Class kClass{"kClass"}; +static constexpr Class kClass2{"kClass2"}; + +TEST_F(JniTest, LocalObject_AllowsNullPtrT) { + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + + LocalObject obj{nullptr}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_DoesntTryToDeleteNull) { + EXPECT_CALL(*env_, NewLocalRef).Times(0); + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + + LocalObject obj{jobject{nullptr}}; + EXPECT_EQ(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_CallsNewAndDeleteOnNewObject) { + EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake())); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(1); + + LocalObject obj{}; + EXPECT_EQ(jobject{obj}, Fake()); +} + +TEST_F(JniTest, LocalObject_CallsOnlyDeleteOnWrapCtor) { + EXPECT_CALL(*env_, NewLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))) + .Times(1); + + LocalObject obj{Fake()}; + EXPECT_NE(jobject{obj}, nullptr); +} + +TEST_F(JniTest, LocalObject_CallsNewLocalRefByDefault) { + EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + + LocalObject obj{Fake(1)}; + EXPECT_EQ(jobject{obj}, Fake(2)); +} + +TEST_F(JniTest, LocalObject_CallsNewLocalRefOnCopy) { + EXPECT_CALL(*env_, NewLocalRef).WillOnce(::testing::Return(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + + LocalObject obj{NewRef{}, Fake(1)}; + EXPECT_EQ(jobject{obj}, Fake(2)); +} + +TEST_F(JniTest, LocalObject_ObjectReturnsInstanceMethods) { + // This test doesn't use the default JniTest helpers to be a little more + // explicit about exactly what calls must be made in what order. + static constexpr Class kClass{ + "com/google/AnotherClass", + Method{"Foo", jni::Return{}, Params{}}, + Method{"Baz", jni::Return{}, Params{}}, + Method{"AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplates" + "ToHandle", + jni::Return{}, + Params{}}}; + + InSequence seq; + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(1); + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), StrEq("()V"))) + .WillOnce(testing::Return(Fake(1))); + EXPECT_CALL(*env_, NewObjectV(_, Fake(1), _)) + .WillOnce(testing::Return(Fake())); + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)I"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, + GetMethodID(_, + StrEq("AMethodWithAReallyLongNameThatWouldPossiblyBeH" + "ardForTemplatesToHandle"), + StrEq("(IFIFD)D"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(1); + + LocalObject obj{}; + obj("Foo", 12345); + obj("Baz", 12345.f); + obj("AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", + 12345, 12345.f, 12345, 12345.f, jdouble{12345}); +} + +TEST_F(JniTest, LocalObject_CallsDeleteOnceAfterAMoveConstruction) { + EXPECT_CALL(*env_, NewLocalRef).Times(1); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))) + .Times(1); + + LocalObject obj_1{Fake()}; + + EXPECT_NE(jobject{obj_1}, nullptr); + + LocalObject obj_2{std::move(obj_1)}; + + EXPECT_NE(jobject{obj_2}, nullptr); +} + +TEST_F(JniTest, LocalObject_FunctionsProperlyInSTLContainer) { + EXPECT_CALL(*env_, NewLocalRef).Times(2); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake(1)))); + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake(2)))); + + LocalObject obj_1{Fake(1)}; + LocalObject obj_2{Fake(2)}; + std::tuple t{std::move(obj_1), std::move(obj_2)}; +} + +TEST_F(JniTest, LocalObject_ValuesWorkAfterMoveConstructor) { + static constexpr Class kClass{ + "com/google/ValuesWorkAfterMoveConstructor", + Method{"Foo", jni::Return{}, Params{}}, + Field{"BarField", jint{}}}; + + EXPECT_CALL(*env_, CallIntMethodV).Times(3); + EXPECT_CALL(*env_, SetIntField).Times(4); + + LocalObject obj_1{Fake()}; + obj_1("Foo", 1); + obj_1("Foo", 2); + obj_1["BarField"].Set(1); + + LocalObject obj_2{std::move(obj_1)}; + obj_2("Foo", 3); + obj_2["BarField"].Set(2); + obj_2["BarField"].Set(3); + obj_2["BarField"].Set(4); +} + +TEST_F(JniTest, LocalObject_ReleasesLocalsForAlternateConstructors) { + static constexpr Class kClass{"ReleasesLocalsForAlternateConstructors", + jni::Constructor{}}; + LocalObject g1{1}; + LocalObject g2{2}; + LocalObject g3{3}; + EXPECT_CALL(*env_, DeleteLocalRef(_)).Times(3); +} + +TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects) { + LocalObject val_1{Fake(1)}; + LocalObject val_2{Fake(2)}; + + EXPECT_TRUE(val_1 == val_1); + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + EXPECT_TRUE(val_2 == val_2); + EXPECT_TRUE(val_2 != val_1); + EXPECT_FALSE(val_1 == val_2); +} + +TEST_F(JniTest, LocalObject_ComparesAgainstjobjects) { + static constexpr Class kClass{"kClass1"}; + LocalObject val_1{Fake()}; + + EXPECT_TRUE(val_1 == AsNewLocalReference(Fake())); + EXPECT_TRUE(AsNewLocalReference(Fake()) == val_1); + + EXPECT_FALSE(val_1 != AsNewLocalReference(Fake())); + EXPECT_FALSE(AsNewLocalReference(Fake()) != val_1); +} + +struct A { + LocalObject val_1; + LocalObject val_2; + + A(LocalObject&& val_1, LocalObject&& val_2) + : val_1(std::move(val_1)), val_2(std::move(val_2)) {} + + A(A&& rhs) : val_1(std::move(rhs.val_1)), val_2(std::move(rhs.val_2)) {} +}; + +constexpr bool operator==(const A& lhs, const A& rhs) { + return lhs.val_1 == rhs.val_1 && lhs.val_2 == rhs.val_2; +} + +constexpr bool operator!=(const A& lhs, const A& rhs) { + return lhs.val_1 != rhs.val_1 || lhs.val_2 != rhs.val_2; +} + +TEST_F(JniTest, LocalObject_ComparesAgainstOtherLocalObjects_InContainers) { + A val_1{LocalObject{Fake(1)}, {Fake(2)}}; + A val_2{LocalObject{Fake(1)}, {Fake(3)}}; + + EXPECT_FALSE(val_1 == val_2); + EXPECT_TRUE(val_1 != val_2); + + EXPECT_EQ((std::array{A{LocalObject(Fake(1)), + LocalObject(Fake(2))}}), + (std::array{A{LocalObject(Fake(1)), + LocalObject(Fake(2))}})); + + EXPECT_TRUE((std::array{A{LocalObject(Fake(1)), + LocalObject(Fake(2))}} != + (std::array{A{LocalObject(Fake(1)), + LocalObject(Fake(3))}}))); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnLvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject a{}; + LocalObject b{}; + b("Foo", a); +} + +TEST_F(JniTest, LocalObject_SupportsReturningAClass) { + static constexpr Class kClass{ + "Class1", Method{"Foo", jni::Return{kClass2}, jni::Params{}}}; + + LocalObject a{}; + a("Foo"); +} + +TEST_F(JniTest, LocalObject_SupportsReturningAString) { + static constexpr Class kClass{ + "Class1", Method{"Foo", jni::Return{}, jni::Params{}}}; + + LocalObject a{}; + a("Foo"); +} + +jobject ReturnOutputOfMethod() { + static constexpr Class kClass2{"Class2", Method{"Foo", jni::Return{kClass}}}; + + return LocalObject{}("Foo").Release(); +} + +TEST_F(JniTest, LocalObject_CompilesWhenReturnReleasing) { + ReturnOutputOfMethod(); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnPrvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject a{}; + LocalObject b{}; + b("Foo", std::move(a)); +} + +TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnXvalue) { + static constexpr Class kClass2{ + "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; + + LocalObject b{}; + b("Foo", LocalObject{}); +} + +TEST_F(JniTest, LocalObject_MovesInContainerStruct) { + struct A { + const LocalObject val; + + A(LocalObject&& in) : val(std::move(in)) {} + }; + + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + + A a{LocalObject{Fake()}}; +} + +TEST_F(JniTest, LocalObject_DoesntCrossTalkOverClassMethodIds) { + static constexpr Class kClass{ + "kClass", Method{"Foo", jni::Return{}, jni::Params{}}}; + + static constexpr Class kClass2{ + "kClass2", Method{"Foo", jni::Return{}, jni::Params{}}}; + + EXPECT_CALL(*env_, GetMethodID(_, _, StrEq("(I)V"))).Times(2); + + LocalObject obj_1{Fake(1)}; + LocalObject obj_2{Fake(2)}; + + // These are different method IDs (they are different classes). + obj_1("Foo", 1); + obj_2("Foo", 1); +} + +} // namespace diff --git a/implementation/legacy/method_ref_test.cc b/implementation/legacy/method_ref_test.cc new file mode 100644 index 00000000..fc9248bc --- /dev/null +++ b/implementation/legacy/method_ref_test.cc @@ -0,0 +1,301 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::GlobalObject; +using ::jni::Id; +using ::jni::IdType; +using ::jni::JniT; +using ::jni::kDefaultClassLoader; +using ::jni::kNoIdx; +using ::jni::LocalArray; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::OverloadRef; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Return; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::StrEq; + +template +using MethodRefT_t = OverloadRef, + IdType::OVERLOAD, I, 0, kNoIdx, 0>, + IdType::OVERLOAD_PARAM>; + +TEST_F(JniTest, MethodRef_DoesntStaticCrossTalkWithTagUse) { + static constexpr Method m{"FooV", Return{}, Params{jint{}}}; + static constexpr Class kSomeClass{"someClass", m}; + + MethodRefT_t::Invoke( + Fake(), Fake(), 123); +} + +TEST_F(JniTest, MethodRef_CallsGetMethodCorrectlyForSingleMethod) { + static constexpr Method m1{"FooV", Return{}}; + static constexpr Class c{"SimpleClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake()), StrEq("FooV"), StrEq("()V"))) + .WillOnce(testing::Return(Fake())); + EXPECT_CALL(*env_, CallVoidMethodV(Fake(), Fake(), _)); + + MethodRefT_t::Invoke(Fake(), + Fake()); +} + +TEST_F(JniTest, MethodRef_ReturnWithObject) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{c2}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake()), StrEq("FooV"), + StrEq("()LsomeClass2;"))) + .WillOnce(testing::Return(Fake())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake(), Fake(), _)); + + MethodRefT_t::Invoke(Fake(), + Fake()); +} + +TEST_F(JniTest, MethodRef_ReturnWithRank1Object) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{Array{c2}}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake()), StrEq("FooV"), + StrEq("()[LsomeClass2;"))) + .WillOnce(testing::Return(Fake())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake(), Fake(), _)); + + MethodRefT_t::Invoke(Fake(), + Fake()); +} + +TEST_F(JniTest, MethodRef_ReturnWithRank2Object) { + static constexpr Class c2{"someClass2"}; + static constexpr Method m1{"FooV", Return{Array{c2, Rank<2>{}}}, Params<>{}}; + static constexpr Class c{"someClass", m1}; + + InSequence seq; + EXPECT_CALL(*env_, GetMethodID(Eq(Fake()), StrEq("FooV"), + StrEq("()[[LsomeClass2;"))) + .WillOnce(testing::Return(Fake())); + EXPECT_CALL(*env_, CallObjectMethodV(Fake(), Fake(), _)); + + MethodRefT_t::Invoke(Fake(), + Fake()); +} + +TEST_F(JniTest, MethodRef_ReturnWithNoParams) { + static constexpr Method m1{"FooV", Return{}}; + static constexpr Method m2{"BarI", Return{}}; + static constexpr Method m3{"BazF", Return{}}; + static constexpr Class c{"someClass", m1, m2, m3}; + + InSequence seq; + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake()), StrEq("FooV"), StrEq("()V"))) + .WillOnce(testing::Return(Fake(1))); + EXPECT_CALL(*env_, CallVoidMethodV(Fake(), Fake(1), _)); + + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake()), StrEq("BarI"), StrEq("()I"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, CallIntMethodV(Fake(), Fake(2), _)); + + EXPECT_CALL(*env_, + GetMethodID(Eq(Fake()), StrEq("BazF"), StrEq("()F"))) + .WillOnce(testing::Return(Fake(3))); + EXPECT_CALL(*env_, CallFloatMethodV(Fake(), Fake(3), _)); + + MethodRefT_t::Invoke(Fake(), + Fake()); + MethodRefT_t::Invoke(Fake(), + Fake()); + MethodRefT_t::Invoke(Fake(), + Fake()); +} + +TEST_F(JniTest, MethodRef_SingleParam) { + constexpr Method m1{"SomeFunc1", Return{}, Params{}}; + constexpr Method m2{"SomeFunc2", Return{}, Params{}}; + constexpr Method m3{"SomeFunc3", Return{}, Params{}}; + static constexpr Class c{"someClass", m1, m2, m3}; + + InSequence seq; + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake()), StrEq("SomeFunc1"), StrEq("(I)V"))) + .WillOnce(testing::Return(Fake(1))); + // There is no clear way to test variable vaargs type arguments using Gmock, + // but at least we can test the correct method is called. + EXPECT_CALL(*env_, CallVoidMethodV(Fake(), Fake(1), _)); + + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake()), StrEq("SomeFunc2"), StrEq("(F)I"))) + .WillOnce(testing::Return(Fake(2))); + EXPECT_CALL(*env_, CallIntMethodV(Fake(), Fake(2), _)); + + EXPECT_CALL( + *env_, GetMethodID(Eq(Fake()), StrEq("SomeFunc3"), StrEq("(F)F"))) + .WillOnce(testing::Return(Fake(3))); + EXPECT_CALL(*env_, CallFloatMethodV(Fake(), Fake(3), _)); + + MethodRefT_t::Invoke(Fake(), + Fake(), 1); + MethodRefT_t::Invoke(Fake(), + Fake(), 1.234f); + MethodRefT_t::Invoke(Fake(), + Fake(), 5.6789f); +} + +TEST_F(JniTest, MethodRef_ReturnsObjects) { + static constexpr Class c1{"Bazz"}; + static constexpr Class kClass{ + "com/google/ReturnsObjects", + Method{"Foo", Return{c1}, Params{}}, + }; + + // Note, class refs are not released, so Times() != 2. + EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake())); + + GlobalObject global_object{}; + LocalObject new_obj{global_object("Foo", 5)}; +} + +TEST_F(JniTest, MethodRef_PassesObjects) { + static constexpr Class c1{"com/google/Bazz"}; + static constexpr Class kClass{ + "com/google/PassesObjects", + Method{"Foo", Return{}, Params{c1}}, + }; + + LocalObject local_object{Fake()}; + GlobalObject global_object{AdoptGlobal{}, Fake(100)}; + + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Lcom/google/Bazz;)I"))); + + global_object("Foo", local_object); +} + +TEST_F(JniTest, MethodRef_PassesAndReturnsMultipleObjects) { + static constexpr Class c1{"Class1"}; + static constexpr Class c2{"Class2"}; + static constexpr Class c3{"Class3"}; + static constexpr Class c4{"Class4"}; + + static constexpr Class class_under_test{ + "com/google/PassesAndReturnsMultipleObjects", + Method{"Foo", Return{c1}, Params{c1, c2, c3, c4}}, + }; + + LocalObject obj1{Fake(1)}; + LocalObject obj2{Fake(2)}; + LocalObject obj3{Fake(3)}; + LocalObject obj4{Fake(4)}; + LocalObject object_under_test{Fake(5)}; + + LocalObject obj5{object_under_test("Foo", obj1, obj2, obj3, obj4)}; +} + +TEST_F(JniTest, MethodRef_SupportsForwardDefines) { + static constexpr Class kClass1{ + "kClass1", + Method{"m1", Return{}, Params{Class{"kClass1"}}}, + Method{"m2", Return{}, Params{Class{"kClass2"}}}, + Method{"m3", Return{Class{"kClass1"}}}, + Method{"m4", Return{Class{"kClass2"}}}, + }; + + static constexpr Class kClass2{ + "kClass2", + Method{"m1", Return{}, Params{Class{"kClass1"}}}, + Method{"m2", Return{}, Params{Class{"kClass2"}}}, + Method{"m3", Return{Class{"kClass1"}}}, + Method{"m4", Return{Class{"kClass2"}}}, + }; + + LocalObject c1_obj1{Fake(1)}; + LocalObject c1_obj2{Fake(2)}; + + LocalObject c2_obj1{Fake(3)}; + LocalObject c2_obj2{Fake(4)}; + + c1_obj1("m1", c1_obj1); + c1_obj1("m2", c2_obj1); + c1_obj1("m1", c1_obj1("m3")); + c1_obj1("m2", c1_obj1("m4")); + + c2_obj1("m1", c1_obj1); + c2_obj1("m2", c2_obj2); + c2_obj1("m2", std::move(std::move(c2_obj2))); + + c1_obj1("m2", std::move(c2_obj1)); + + // c2_obj1("m1", c1_obj1); // illegal! triggers warnings (post move read). + // c2_obj1("m2", c2_obj2); // illegal! triggers warnings (post move read). + // c2_obj1("m2", std::move(c2_obj2)); // illegal! triggers warnings (post + // move read). +} + +TEST_F(JniTest, MethodRef_SupportsStrings) { + static constexpr Class class_under_test{ + "com/google/SupportsStrings", + Method{"Foo", Return{}, Params{}}, + Method{"Bar", Return{}, Params{}}, + Method{"Baz", Return{}}, + }; + + LocalObject obj1{Fake()}; + obj1("Foo", "This is a method."); + obj1("Bar", "This is a method.", "It takes strings"); + obj1("Baz"); +} + +TEST_F(JniTest, MethodRef_SupportsArrays) { + static constexpr Class kClass{"kClass"}; + static constexpr Class class_under_test{ + "com/google/SupportsArrays", + Method{"Foo", Return{}, Params{Array{kClass}}}, + Method{"Bar", Return{}, Params{}}}; + + LocalArray local_array{nullptr}; + LocalObject obj1{Fake()}; + obj1("Foo", local_array); +} + +} // namespace diff --git a/implementation/legacy/multi_type_test.cc b/implementation/legacy/multi_type_test.cc new file mode 100644 index 00000000..70c323de --- /dev/null +++ b/implementation/legacy/multi_type_test.cc @@ -0,0 +1,114 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::Method; +using ::jni::Params; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Eq; +using ::testing::Return; +using ::testing::StrEq; + +TEST_F(JniTest, MultiTypeTest_SimpleSmokeTestForSingleObject) { + static constexpr Class object{ + "ARCore", + Method{"Foo", jni::Return{}, Params{}}, + Method{"Bar", jni::Return{jint{}}}, + Method{"Baz", jni::Return{}, Params{}}, + Field{"SomeField", jint{}}, + }; + + EXPECT_CALL(*env_, FindClass(StrEq("ARCore"))) + .WillOnce(Return(Fake(1))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))); + EXPECT_CALL(*env_, NewGlobalRef(Eq(Fake(1)))) + .WillOnce(Return(Fake(2))); + EXPECT_CALL(*env_, + GetMethodID(Fake(2), StrEq(""), StrEq("()V"))) + .WillOnce(Return(Fake())); + + EXPECT_CALL(*env_, NewObjectV(Fake(2), Fake(), _)) + .WillOnce(Return(Fake(1))); + EXPECT_CALL(*env_, NewGlobalRef(Fake(1))) + .WillOnce(Return(Fake(2))); + EXPECT_CALL(*env_, DeleteLocalRef(Fake(1))).Times(1); + + EXPECT_CALL(*env_, GetMethodID(Fake(2), StrEq("Foo"), StrEq("(IF)I"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetMethodID(Fake(2), StrEq("Bar"), StrEq("()I"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetMethodID(Fake(2), StrEq("Baz"), StrEq("(F)V"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, + GetFieldID(Fake(2), StrEq("SomeField"), StrEq("I"))) + .WillOnce(Return(Fake())); + + EXPECT_CALL(*env_, DeleteGlobalRef(Fake(2))).Times(1); + EXPECT_CALL(*env_, DeleteGlobalRef(Fake(2))).Times(1); + + jni::GlobalObject obj{}; + obj("Foo", 1, 2.f); + obj("Baz", 1.f); + obj("Baz", 1.f); + obj("Baz", 2.f); + obj("Baz", 3.f); + obj("Bar"); + obj["SomeField"].Get(); +} + +TEST_F(JniTest, MultiTypeTest_MethodsOfSameNameButDifferentClassAreUnique) { + EXPECT_CALL(*env_, GetMethodID).Times(2); + + static constexpr Class c1{ + "com/google/ARCore", + Method{"Foo", jni::Return{}, Params{}}, + }; + static constexpr Class c2{ + "com/google/VRCore", + Method{"Foo", jni::Return{}, Params{}}, + }; + + jni::LocalObject obj1{Fake(1)}; + jni::LocalObject obj2{Fake(2)}; + obj1("Foo", 12345); + obj2("Foo", 12345); + + // All of these calls ought not query for a method ID again. + obj1("Foo", 12345); + obj1("Foo", 12345); + obj1("Foo", 12345); + obj1("Foo", 12345); + obj2("Foo", 12345); + obj2("Foo", 12345); + obj2("Foo", 12345); + + jni::LocalObject obj3{Fake(3)}; + obj3("Foo", 12345); + obj3("Foo", 12345); +} + +} // namespace diff --git a/implementation/legacy/overload_ref_test.cc b/implementation/legacy/overload_ref_test.cc new file mode 100644 index 00000000..afd62155 --- /dev/null +++ b/implementation/legacy/overload_ref_test.cc @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Class; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Overload; +using ::jni::Params; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::StrEq; + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods1) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{"Foo", jni::Return{}, Params{}}, + }; + LocalObject obj{}; + + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + + obj("Foo", "test"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods2) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{"Foo", Overload{jni::Return{}, Params{jint{}}}, + Overload{jni::Return{}, Params{jstring{}}}}}; + LocalObject obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); doesn't compile (good). + obj("Foo", "test"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethods3) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{ + "Foo", + Overload{jni::Return{}, Params{jint{}}}, + Overload{jni::Return{}, Params{jstring{}}}, + Overload{jni::Return{}, Params{jstring{}, jstring{}}}, + }}; + LocalObject obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); // doesn't compile (good). + obj("Foo", "test"); + obj("Foo", "test1", "test2"); + obj("Foo", "this_doesnt", "trigger_method_lookup"); +} + +TEST_F(JniTest, MethodRef_AsksForCorrectMethodsWhenMultiplePresent4) { + static constexpr Class kClass{ + "com/google/SupportsStrings", + Method{ + "Foo", + Overload{jni::Return{}, Params{jint{}}}, + Overload{jni::Return{}, Params{jstring{}}}, + Overload{jni::Return{}, Params{jstring{}, jstring{}}}, + }, + Method{ + "Baz", + Overload{jni::Return{}, Params{jint{}}}, + Overload{jni::Return{}, Params{jstring{}, jstring{}}}, + Overload{jni::Return{}, Params{jfloat{}, jfloat{}}}, + }}; + LocalObject obj{}; + + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Foo"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(I)V"))); + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("Baz"), + StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(FF)V"))); + + obj("Foo", 1); + // obj("Foo", 2.f); // doesn't compile (good). + obj("Foo", "test"); + obj("Foo", "test1", "test2"); + obj("Foo", "this_doesnt", "trigger_method_lookup"); + obj("Baz", 1); + obj("Baz", "test3", "test4"); + obj("Baz", 1234.f, 5678.f); +} + +} // namespace diff --git a/implementation/legacy/self_test.cc b/implementation/legacy/self_test.cc new file mode 100644 index 00000000..7deee5d6 --- /dev/null +++ b/implementation/legacy/self_test.cc @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::Class; +using ::jni::Fake; +using ::jni::LocalObject; +using ::jni::Method; +using ::jni::Params; +using ::jni::Return; +using ::jni::Self; +using ::jni::test::AsGlobal; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::StrEq; + +static constexpr Class kClass{"Builder", Method{"build", Return{Self{}}}, + Method{"takesBuilder", Return{}, Params{Self{}}}}; + +TEST_F(JniTest, SelfCanBeUsedAsAReturnValue) { + EXPECT_CALL(*env_, FindClass(StrEq("Builder"))) + .WillOnce(::testing::Return(Fake())); + EXPECT_CALL(*env_, GetMethodID(AsGlobal(Fake()), StrEq("build"), + StrEq("()LBuilder;"))); + + LocalObject obj{Fake()}; + obj("build"); +} + +TEST_F(JniTest, SelfCanBeUsedAsAReturnValueAndMaintainsRichDecoration) { + EXPECT_CALL(*env_, GetMethodID).Times(AnyNumber()); + EXPECT_CALL(*env_, GetMethodID(_, StrEq("build"), StrEq("()LBuilder;"))); + + LocalObject obj{Fake()}; + obj("build")("build")("build"); +} + +TEST_F(JniTest, SelfCanBeUsedAsParam) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq("takesBuilder"), StrEq("(LBuilder;)V"))); + + LocalObject obj_1{Fake(1)}; + LocalObject obj_2{Fake(2)}; + + obj_1("takesBuilder", obj_2); +} + +} // namespace diff --git a/implementation/legacy/static_ref_test.cc b/implementation/legacy/static_ref_test.cc new file mode 100644 index 00000000..b94c5a4a --- /dev/null +++ b/implementation/legacy/static_ref_test.cc @@ -0,0 +1,373 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptLocal; +using ::jni::Array; +using ::jni::Class; +using ::jni::Fake; +using ::jni::Field; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::Method; +using ::jni::Params; +using ::jni::Rank; +using ::jni::Self; +using ::jni::Static; +using ::jni::StaticRef; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +//////////////////////////////////////////////////////////////////////////////// +// Fields. +//////////////////////////////////////////////////////////////////////////////// + +static constexpr Class kClass2{"kClass2"}; + +// clang-format off +static constexpr Class kClass{ + "kClass", + Static { + Field{"booleanField", jboolean{}}, + Field{"byteField", jbyte{}}, + Field{"charField", jchar{}}, + Field{"shortField", jshort{}}, + Field{"intField", jint{}}, + Field{"longField", jlong{}}, + Field{"floatField", jfloat{}}, + Field{"doubleField", jdouble{}}, + Field{"stringField", jstring{}}, + Field{"classField", Class{"kClass2"}}, + Field{"selfField", Self{}} + }, +}; +// clang-format on + +TEST_F(JniTest, StaticBooleanField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("booleanField"), StrEq("Z"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticBooleanField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticBooleanField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["booleanField"].Get()); + StaticRef{}["booleanField"].Set(true); +} + +TEST_F(JniTest, StaticByteField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("byteField"), StrEq("B"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticByteField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticByteField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["byteField"].Get()); + StaticRef{}["byteField"].Set(true); +} + +TEST_F(JniTest, StaticCharField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("charField"), StrEq("C"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticCharField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticCharField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["charField"].Get()); + StaticRef{}["charField"].Set(true); +} + +TEST_F(JniTest, StaticShortField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("shortField"), StrEq("S"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticShortField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticShortField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["shortField"].Get()); + StaticRef{}["shortField"].Set(true); +} + +TEST_F(JniTest, StaticIntField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("intField"), StrEq("I"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticIntField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticIntField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["intField"].Get()); + StaticRef{}["intField"].Set(true); +} + +TEST_F(JniTest, StaticLongField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("longField"), StrEq("J"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticLongField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticLongField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["longField"].Get()); + StaticRef{}["longField"].Set(true); +} + +TEST_F(JniTest, StaticFloatField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("floatField"), StrEq("F"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticFloatField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticFloatField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["floatField"].Get()); + StaticRef{}["floatField"].Set(true); +} + +TEST_F(JniTest, StaticDoubleField) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("doubleField"), StrEq("D"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticDoubleField(_, Fake())) + .WillOnce(Return(true)); + EXPECT_CALL(*env_, SetStaticDoubleField(_, Fake(), true)); + + LocalObject obj; + EXPECT_TRUE(StaticRef{}["doubleField"].Get()); + StaticRef{}["doubleField"].Set(true); +} + +TEST_F(JniTest, StaticField_ObjectGet) { + EXPECT_CALL(*env_, + GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticObjectField(_, Fake())) + .WillOnce(Return(Fake())); + + jni::LocalObject obj = StaticRef{}["classField"].Get(); +} + +TEST_F(JniTest, StaticField_StringSet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("stringField"), + StrEq("Ljava/lang/String;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake(), Fake())); + + StaticRef{}["stringField"].Set( + LocalString{AdoptLocal{}, Fake()}); +} + +TEST_F(JniTest, StaticField_ObjectSet) { + EXPECT_CALL(*env_, + GetStaticFieldID(_, StrEq("classField"), StrEq("LkClass2;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake(), Fake())); + + StaticRef{}["classField"].Set( + LocalObject{AdoptLocal{}, Fake()}); +} + +TEST_F(JniTest, StaticField_SelfGet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, GetStaticObjectField(_, Fake())) + .WillOnce(Return(Fake())); + + jni::LocalObject obj = StaticRef{}["selfField"].Get(); +} + +TEST_F(JniTest, StaticField_SelfSet) { + EXPECT_CALL(*env_, GetStaticFieldID(_, StrEq("selfField"), StrEq("LkClass;"))) + .WillOnce(Return(Fake())); + EXPECT_CALL(*env_, + SetStaticObjectField(_, Fake(), Fake())); + + StaticRef{}["selfField"].Set( + LocalObject{AdoptLocal{}, Fake()}); +} + +//////////////////////////////////////////////////////////////////////////////// +// Static Methods. +//////////////////////////////////////////////////////////////////////////////// + +// clang-format off +static constexpr Class kMethodClass{ + "kMethodClass", + Static { + Method{"booleanMethod", ::jni::Return{jboolean{}}}, + Method{"byteMethod", ::jni::Return{jbyte{}}}, + Method{"charMethod", ::jni::Return{jchar{}}}, + Method{"shortMethod", ::jni::Return{jshort{}}}, + Method{"intMethod", ::jni::Return{jint{}}}, + Method{"longMethod", ::jni::Return{jlong{}}}, + Method{"floatMethod", ::jni::Return{jfloat{}}}, + Method{"doubleMethod", ::jni::Return{jdouble{}}}, + Method{"stringMethod", ::jni::Return{jstring{}}}, + Method{"objectMethod", ::jni::Return{Class{"kClass2"}}}, + Method{"rank1ArrayMethod", ::jni::Return{Array{Class{"kClass2"}}}}, + Method{"rank2ArrayMethod", ::jni::Return{Array{Class{"kClass2"}, Rank<2>{}}}}, + Method{"selfMethod", ::jni::Return{Self{}}}, + + Method{"simpleFunc", ::jni::Return{int{}}, Params{jfloat{}}}, + Method{"complexFunc", ::jni::Return{float{}}, + Params{ + Array{Class{"kClass2"}, Rank<2>{}}, int{}, float{}, Class{"kClass3"}, + } + } + }, +}; +// clang-format on + +TEST_F(JniTest, StaticExerciseAllReturns) { + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("()Z"))); + EXPECT_CALL(*env_, CallStaticBooleanMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("()B"))); + EXPECT_CALL(*env_, CallStaticByteMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("()C"))); + EXPECT_CALL(*env_, CallStaticCharMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("()S"))); + EXPECT_CALL(*env_, CallStaticShortMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("()I"))); + EXPECT_CALL(*env_, CallStaticIntMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("()J"))); + EXPECT_CALL(*env_, CallStaticLongMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("()F"))); + EXPECT_CALL(*env_, CallStaticFloatMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("()D"))); + EXPECT_CALL(*env_, CallStaticDoubleMethodV); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"), + StrEq("()Ljava/lang/String;"))); + + EXPECT_CALL( + *env_, GetStaticMethodID(_, StrEq("objectMethod"), StrEq("()LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"), + StrEq("()[LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"), + StrEq("()[[LkClass2;"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"), + StrEq("()LkMethodClass;"))); + + EXPECT_CALL(*env_, CallStaticObjectMethodV).Times(5); + + StaticRef{}("booleanMethod"); + StaticRef{}("byteMethod"); + StaticRef{}("charMethod"); + StaticRef{}("shortMethod"); + StaticRef{}("intMethod"); + StaticRef{}("longMethod"); + StaticRef{}("floatMethod"); + StaticRef{}("doubleMethod"); + StaticRef{}("stringMethod"); + + StaticRef{}("objectMethod"); + StaticRef{}("rank1ArrayMethod"); + StaticRef{}("rank2ArrayMethod"); + + LocalObject self_ret = StaticRef{}("selfMethod"); +} + +// clang-format off +static constexpr Class kMethodClassSingleParam{ + "kMethodClassSingleParam", + Static { + Method{"booleanMethod", ::jni::Return{}, Params{}}, + Method{"byteMethod", ::jni::Return{}, Params{}}, + Method{"charMethod", ::jni::Return{}, Params{}}, + Method{"shortMethod", ::jni::Return{}, Params{}}, + Method{"intMethod", ::jni::Return{}, Params{}}, + Method{"longMethod", ::jni::Return{}, Params{}}, + Method{"floatMethod", ::jni::Return{}, Params{}}, + Method{"doubleMethod", ::jni::Return{}, Params{}}, + Method{"stringMethod", ::jni::Return{}, Params{}}, + Method{"objectMethod", ::jni::Return{}, Params{Class{"kClass2"}}}, + Method{"rank1ArrayMethod", ::jni::Return{}, Params{Array{Class{"kClass2"}}}}, + Method{"rank2ArrayMethod", ::jni::Return{}, Params{Array{Class{"kClass2"}, Rank<2>{}}}}, + Method{"selfMethod", ::jni::Return{}, Params{Self{}}} + }, +}; +// clang-format on + +TEST_F(JniTest, StaticExerciseAllTypesThroughSingleParam) { + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("booleanMethod"), StrEq("(Z)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("byteMethod"), StrEq("(B)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("charMethod"), StrEq("(C)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("shortMethod"), StrEq("(S)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("intMethod"), StrEq("(I)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("longMethod"), StrEq("(J)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("floatMethod"), StrEq("(F)V"))); + EXPECT_CALL(*env_, + GetStaticMethodID(_, StrEq("doubleMethod"), StrEq("(D)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("stringMethod"), + StrEq("(Ljava/lang/String;)V"))); + + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("objectMethod"), + StrEq("(LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank1ArrayMethod"), + StrEq("([LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("rank2ArrayMethod"), + StrEq("([[LkClass2;)V"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("selfMethod"), + StrEq("(LkMethodClassSingleParam;)V"))); + + EXPECT_CALL(*env_, CallStaticVoidMethodV).Times(13); + + StaticRef{}("booleanMethod", jboolean{true}); + StaticRef{}("byteMethod", jbyte{1}); + StaticRef{}("charMethod", jchar{'a'}); + StaticRef{}("shortMethod", jshort{1}); + StaticRef{}("intMethod", jint{123}); + StaticRef{}("longMethod", jlong{456}); + StaticRef{}("floatMethod", jfloat{789.f}); + StaticRef{}("doubleMethod", jdouble{101.}); + StaticRef{}("stringMethod", "test"); + + // It would be more complete to exercise all types here. + StaticRef{}("objectMethod", Fake()); + StaticRef{}("rank1ArrayMethod", + Fake()); + StaticRef{}("rank2ArrayMethod", + Fake()); + StaticRef{}("selfMethod", Fake()); +} + +TEST_F(JniTest, StaticExerciseComplexSetOfParams) { + // The primary difference for statics are how they handle their returns. + // Coverage is already fairly extensive for params. + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("simpleFunc"), StrEq("(F)I"))); + EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("complexFunc"), + StrEq("([[LkClass2;IFLkClass3;)F"))); + + StaticRef{}("simpleFunc", 123.f); + StaticRef{}("complexFunc", jobjectArray{nullptr}, 123, 456.f, + jobject{nullptr}); +} + +} // namespace diff --git a/implementation/legacy/string_ref_test.cc b/implementation/legacy/string_ref_test.cc new file mode 100644 index 00000000..17bfd6d3 --- /dev/null +++ b/implementation/legacy/string_ref_test.cc @@ -0,0 +1,238 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include "implementation/jni_helper/fake_test_constants.h" +#include "jni_bind.h" +#include "jni_test.h" + +namespace { + +using ::jni::AdoptGlobal; +using ::jni::Fake; +using ::jni::GlobalObject; +using ::jni::GlobalString; +using ::jni::kJavaLangString; +using ::jni::LocalObject; +using ::jni::LocalString; +using ::jni::NewRef; +using ::jni::UtfStringView; +using ::jni::test::AsNewLocalReference; +using ::jni::test::JniTest; +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +const char* char_ptr = "TestString"; + +static constexpr jni::Class kClass{ + "Class", + jni::Method{"Foo", jni::Return{}, jni::Params{}}, + jni::Method{"TakesStrParam", jni::Return{}, jni::Params{}}, +}; + +//////////////////////////////////////////////////////////////////////////////// +// Local String Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, LocalString_NullPtrT) { LocalString str{nullptr}; } + +TEST_F(JniTest, LocalString_IsImplicitlyConvertible) { + LocalString str{Fake()}; + EXPECT_EQ(static_cast(str), AsNewLocalReference(Fake())); +} + +TEST_F(JniTest, LocalString_NullWorks) { + EXPECT_CALL(*env_, DeleteLocalRef).Times(0); + LocalString str{jstring{nullptr}}; +} + +TEST_F(JniTest, LocalString_ConstructsFromObject) { + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + LocalObject undecorated_object{Fake()}; + LocalString decorated_object{std::move(undecorated_object)}; +} + +TEST_F(JniTest, LocalString_CopiesFromObject) { + EXPECT_CALL(*env_, DeleteLocalRef(AsNewLocalReference(Fake()))); + EXPECT_CALL(*env_, NewLocalRef(Fake())); + + LocalString decorated_object{NewRef{}, Fake()}; + + EXPECT_EQ(jstring{decorated_object}, AsNewLocalReference(Fake())); +} + +TEST_F(JniTest, LocalString_CopiesFromJString) { + EXPECT_CALL(*env_, DeleteLocalRef(Fake(2))); + EXPECT_CALL(*env_, NewLocalRef(Fake(1))) + .WillOnce(::testing::Return(Fake(2))); + + LocalString decorated_object{NewRef{}, Fake(1)}; + + EXPECT_EQ(jstring{decorated_object}, Fake(2)); +} + +TEST_F(JniTest, LocalString_ConstructsFromOutputOfMethod) { + LocalObject obj{}; + LocalString str{obj("Foo")}; +} + +TEST_F(JniTest, LocalString_ConstructsFromByteArray) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), StrEq("([B)V"))); + LocalString str{Fake()}; +} + +TEST_F(JniTest, LocalString_CreatesFromCharPtr) { + LocalString str{"TestString"}; +} + +TEST_F(JniTest, LocalString_CreatesFromStringView) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake())); + + // jclass for temp String class reference. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + // The variable str (which is itself an object). + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + // TODO(b/143908983): Currently strings leak one local during proxying. + // Temporary xref created during construction. + // EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + + LocalString str{std::string_view{char_ptr}}; +} + +TEST_F(JniTest, LocalString_CreatesFromString) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake())); + + // jclass for temp String class reference. + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + // The variable str (which is itself an object). + EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + // TODO(b/143908983): Currently strings leak one local during proxying. + // Temporary xref created during construction. + // EXPECT_CALL(*env_, DeleteLocalRef(Fake())); + LocalString str{std::string{"TestString"}}; +} + +TEST_F(JniTest, LocalString_CreatesFromCharPtrForGlobals) { + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, LocalString_PinsAndUnpinsMemoryForLocals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq(""), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestLocalString"))); + EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr)); + EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr)); + + LocalString str{"TestLocalString"}; + UtfStringView utf_string_view = str.Pin(); + EXPECT_EQ(utf_string_view.ToString().data(), char_ptr); +} + +TEST_F(JniTest, LocalString_AllowsLValueLocalString) { + LocalObject obj{}; + LocalString local_string{"abcde"}; + obj("TakesStrParam", local_string); +} + +TEST_F(JniTest, LocalString_AllowsRValueLocalString) { + LocalObject obj{}; + obj("TakesStrParam", LocalString{"abcde"}); +} + +//////////////////////////////////////////////////////////////////////////////// +// Global String Tests. +//////////////////////////////////////////////////////////////////////////////// +TEST_F(JniTest, GlobalString_NullWorks) { + GlobalString str{AdoptGlobal{}, jstring{nullptr}}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromObject) { + EXPECT_CALL(*env_, DeleteGlobalRef).Times(1); + GlobalObject undecorated_object{AdoptGlobal{}, + Fake()}; + GlobalString decorated_object{std::move(undecorated_object)}; +} + +TEST_F(JniTest, GlobalString_GlobalsReleaseWithGlobalMechanism) { + EXPECT_CALL(*env_, DeleteGlobalRef); + GlobalString str{AdoptGlobal{}, Fake()}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromOutputOfMethod) { + LocalObject obj{}; + GlobalString str{obj("Foo")}; +} + +TEST_F(JniTest, GlobalString_ConstructsFromByteArray) { + EXPECT_CALL(*env_, GetMethodID(_, StrEq(""), StrEq("([B)V"))); + GlobalString str{Fake()}; +} + +TEST_F(JniTest, GlobalString_CreatesFromCharPtr) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq(""), StrEq("(Ljava/lang/String;)V"))); + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, GlobalString_CreatesFromStringView) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake())); + GlobalString str{std::string_view{char_ptr}}; +} + +TEST_F(JniTest, GlobalString_CreatesFromString) { + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestString"))) + .WillOnce(Return(Fake())); + GlobalString str{std::string{"TestString"}}; +} + +TEST_F(JniTest, GlobalString_CreatesFromCharPtrForGlobals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq(""), StrEq("(Ljava/lang/String;)V"))); + GlobalString str{"TestString"}; +} + +TEST_F(JniTest, GlobalString_PinsAndUnpinsMemoryForLocals) { + EXPECT_CALL(*env_, + GetMethodID(_, StrEq(""), StrEq("(Ljava/lang/String;)V"))); + EXPECT_CALL(*env_, NewStringUTF(StrEq("TestGlobalString"))); + EXPECT_CALL(*env_, GetStringUTFChars(_, nullptr)).WillOnce(Return(char_ptr)); + EXPECT_CALL(*env_, ReleaseStringUTFChars(_, char_ptr)); + + GlobalString str{"TestGlobalString"}; + UtfStringView utf_string_view = str.Pin(); + EXPECT_EQ(utf_string_view.ToString().data(), char_ptr); +} + +TEST_F(JniTest, GlobalString_AllowsLValueGlobalString) { + LocalObject obj{}; + GlobalString global_string{"abcde"}; + obj("TakesStrParam", global_string); +} + +TEST_F(JniTest, GlobalString_AllowsRValueGlobalString) { + LocalObject obj{}; + obj("TakesStrParam", GlobalString{"abcde"}); +} + +} // namespace diff --git a/implementation/local_array_field_test.cc b/implementation/local_array_field_test.cc index e20f60a1..721c86bc 100644 --- a/implementation/local_array_field_test.cc +++ b/implementation/local_array_field_test.cc @@ -69,7 +69,7 @@ TEST_F(JniTest, ArrayField_Object_Iteration_Rank_1) { int i = 100; LocalObject obj{Fake()}; - for (LocalObject a : obj["ObjectArrayRank1"].Get().Pin()) { + for (LocalObject a : obj.Access<"ObjectArrayRank1">().Get().Pin()) { EXPECT_EQ(static_cast(a), Fake(i)); i++; } @@ -87,7 +87,7 @@ TEST_F(JniTest, ArrayField_Object_Iteration_Rank_2) { int i = 100; LocalObject obj{Fake(1)}; for (LocalArray a : - obj["ObjectArrayRank2"].Get().Pin()) { + obj.Access<"ObjectArrayRank2">().Get().Pin()) { EXPECT_EQ(static_cast(a), Fake(i)); i++; } @@ -124,26 +124,27 @@ TEST_F(JniTest, Array_FieldTests) { LocalObject obj{AdoptLocal{}, Fake()}; - EXPECT_EQ(static_cast(obj["BooleanArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"BooleanArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["ByteArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"ByteArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["CharArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"CharArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["ShortArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"ShortArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["IntArray"].Get()), Fake()); - EXPECT_EQ(static_cast(obj["FloatArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"IntArray">().Get()), + Fake()); + EXPECT_EQ(static_cast(obj.Access<"FloatArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["DoubleArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"DoubleArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["LongArray"].Get()), + EXPECT_EQ(static_cast(obj.Access<"LongArray">().Get()), Fake()); - EXPECT_EQ(static_cast(obj["ObjectArrayRank1"].Get()), + EXPECT_EQ(static_cast(obj.Access<"ObjectArrayRank1">().Get()), Fake(1)); - EXPECT_EQ(static_cast(obj["ObjectArrayRank2"].Get()), + EXPECT_EQ(static_cast(obj.Access<"ObjectArrayRank2">().Get()), Fake(2)); - EXPECT_EQ(static_cast(obj["ObjectArrayRank3"].Get()), + EXPECT_EQ(static_cast(obj.Access<"ObjectArrayRank3">().Get()), Fake(3)); } @@ -166,16 +167,18 @@ TEST_F(JniTest, Array_Field_Boolean_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["BooleanArray"].Get()}; + LocalArray arr{obj.Access<"BooleanArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["BooleanArray"].Set(Fake()); - obj["BooleanArray"].Set( + obj.Access<"BooleanArray">().Set(Fake()); + obj.Access<"BooleanArray">().Set( LocalArray{AdoptLocal{}, Fake()}); - obj["BooleanArray"].Set(arr2); - obj["BooleanArray"].Set(obj["BooleanArray"].Get()); + obj.Access<"BooleanArray">().Set(arr2); + obj.Access<"BooleanArray">().Set(obj.Access<"BooleanArray">().Get()); - EXPECT_EQ(obj["BooleanArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["BooleanArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"BooleanArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"BooleanArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Byte_Test) { @@ -197,14 +200,17 @@ TEST_F(JniTest, Array_Field_Byte_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["ByteArray"].Get()}; + LocalArray arr{obj.Access<"ByteArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["ByteArray"].Set(Fake()); - obj["ByteArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["ByteArray"].Set(arr2); - obj["ByteArray"].Set(obj["ByteArray"].Get()); - EXPECT_EQ(obj["ByteArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["ByteArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"ByteArray">().Set(Fake()); + obj.Access<"ByteArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"ByteArray">().Set(arr2); + obj.Access<"ByteArray">().Set(obj.Access<"ByteArray">().Get()); + EXPECT_EQ(obj.Access<"ByteArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"ByteArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Char_Test) { @@ -226,14 +232,17 @@ TEST_F(JniTest, Array_Field_Char_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["CharArray"].Get()}; + LocalArray arr{obj.Access<"CharArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["CharArray"].Set(Fake()); - obj["CharArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["CharArray"].Set(arr2); - obj["CharArray"].Set(obj["CharArray"].Get()); - EXPECT_EQ(obj["CharArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["CharArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"CharArray">().Set(Fake()); + obj.Access<"CharArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"CharArray">().Set(arr2); + obj.Access<"CharArray">().Set(obj.Access<"CharArray">().Get()); + EXPECT_EQ(obj.Access<"CharArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"CharArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Short_Test) { @@ -255,14 +264,17 @@ TEST_F(JniTest, Array_Field_Short_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["ShortArray"].Get()}; + LocalArray arr{obj.Access<"ShortArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["ShortArray"].Set(Fake()); - obj["ShortArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["ShortArray"].Set(arr2); - obj["ShortArray"].Set(obj["ShortArray"].Get()); - EXPECT_EQ(obj["ShortArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["ShortArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"ShortArray">().Set(Fake()); + obj.Access<"ShortArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"ShortArray">().Set(arr2); + obj.Access<"ShortArray">().Set(obj.Access<"ShortArray">().Get()); + EXPECT_EQ(obj.Access<"ShortArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"ShortArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Int_Test) { @@ -283,12 +295,13 @@ TEST_F(JniTest, Array_Field_Int_Test) { Fake(), fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["IntArray"].Get()}; + LocalArray arr{obj.Access<"IntArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["IntArray"].Set(Fake()); - obj["IntArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["IntArray"].Set(arr2); - obj["IntArray"].Set(obj["IntArray"].Get()); + obj.Access<"IntArray">().Set(Fake()); + obj.Access<"IntArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"IntArray">().Set(arr2); + obj.Access<"IntArray">().Set(obj.Access<"IntArray">().Get()); EXPECT_EQ(obj["IntArray"].Get().Pin().ptr(), fake_storage_ptr.get()); EXPECT_EQ(obj["IntArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); } @@ -312,14 +325,17 @@ TEST_F(JniTest, Array_Field_Float_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["FloatArray"].Get()}; + LocalArray arr{obj.Access<"FloatArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["FloatArray"].Set(Fake()); - obj["FloatArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["FloatArray"].Set(arr2); - obj["FloatArray"].Set(obj["FloatArray"].Get()); - EXPECT_EQ(obj["FloatArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["FloatArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"FloatArray">().Set(Fake()); + obj.Access<"FloatArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"FloatArray">().Set(arr2); + obj.Access<"FloatArray">().Set(obj.Access<"FloatArray">().Get()); + EXPECT_EQ(obj.Access<"FloatArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"FloatArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Double_Test) { @@ -341,15 +357,17 @@ TEST_F(JniTest, Array_Field_Double_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["DoubleArray"].Get()}; + LocalArray arr{obj.Access<"DoubleArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["DoubleArray"].Set(Fake()); - obj["DoubleArray"].Set( + obj.Access<"DoubleArray">().Set(Fake()); + obj.Access<"DoubleArray">().Set( LocalArray{AdoptLocal{}, Fake()}); - obj["DoubleArray"].Set(arr2); - obj["DoubleArray"].Set(obj["DoubleArray"].Get()); - EXPECT_EQ(obj["DoubleArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["DoubleArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"DoubleArray">().Set(arr2); + obj.Access<"DoubleArray">().Set(obj.Access<"DoubleArray">().Get()); + EXPECT_EQ(obj.Access<"DoubleArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"DoubleArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Long_Test) { @@ -371,14 +389,17 @@ TEST_F(JniTest, Array_Field_Long_Test) { fake_storage_ptr.get(), JNI_ABORT)); LocalObject obj{AdoptLocal{}, Fake()}; - LocalArray arr{obj["LongArray"].Get()}; + LocalArray arr{obj.Access<"LongArray">().Get()}; LocalArray arr2{AdoptLocal{}, Fake()}; - obj["LongArray"].Set(Fake()); - obj["LongArray"].Set(LocalArray{AdoptLocal{}, Fake()}); - obj["LongArray"].Set(arr2); - obj["LongArray"].Set(obj["LongArray"].Get()); - EXPECT_EQ(obj["LongArray"].Get().Pin().ptr(), fake_storage_ptr.get()); - EXPECT_EQ(obj["LongArray"].Get().Pin(false).ptr(), fake_storage_ptr.get()); + obj.Access<"LongArray">().Set(Fake()); + obj.Access<"LongArray">().Set( + LocalArray{AdoptLocal{}, Fake()}); + obj.Access<"LongArray">().Set(arr2); + obj.Access<"LongArray">().Set(obj.Access<"LongArray">().Get()); + EXPECT_EQ(obj.Access<"LongArray">().Get().Pin().ptr(), + fake_storage_ptr.get()); + EXPECT_EQ(obj.Access<"LongArray">().Get().Pin(false).ptr(), + fake_storage_ptr.get()); } TEST_F(JniTest, Array_Field_Object_Test) { @@ -394,13 +415,13 @@ TEST_F(JniTest, Array_Field_Object_Test) { LocalObject obj{AdoptLocal{}, Fake()}; LocalArray arr2{AdoptLocal{}, Fake()}; - LocalArray arr{obj["ObjectArrayRank1"].Get()}; - obj["ObjectArrayRank1"].Set(Fake()); - obj["ObjectArrayRank1"].Set( + LocalArray arr{obj.Access<"ObjectArrayRank1">().Get()}; + obj.Access<"ObjectArrayRank1">().Set(Fake()); + obj.Access<"ObjectArrayRank1">().Set( LocalArray{AdoptLocal{}, Fake()}); - obj["ObjectArrayRank1"].Set(arr2); - obj["ObjectArrayRank1"].Set(obj["ObjectArrayRank1"].Get()); - obj["ObjectArrayRank1"].Get().Get(2); + obj.Access<"ObjectArrayRank1">().Set(arr2); + obj.Access<"ObjectArrayRank1">().Set(obj.Access<"ObjectArrayRank1">().Get()); + obj.Access<"ObjectArrayRank1">().Get().Get(2); } TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_1) { @@ -414,7 +435,7 @@ TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_1) { EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[LkClass2;"))); LocalObject obj{Fake()}; - LocalArray{obj["Foo"].Get()}; + LocalArray{obj.Access<"Foo">().Get()}; } TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2) { @@ -428,7 +449,7 @@ TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2) { EXPECT_CALL(*env_, GetFieldID(_, StrEq("Foo"), StrEq("[[LkClass2;"))); LocalObject obj{Fake()}; - LocalArray arr_from_field{obj["Foo"].Get()}; + LocalArray arr_from_field{obj.Access<"Foo">().Get()}; } TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2_Iteration) { @@ -471,7 +492,7 @@ TEST_F(JniTest, Array_Field_HandlesLValueLocalObject_Rank_2_Iteration) { LocalObject obj{AdoptLocal{}, Fake(1)}; int i = 100; - for (LocalArray obj : obj["Foo"].Get().Pin()) { + for (LocalArray obj : obj.Access<"Foo">().Get().Pin()) { EXPECT_EQ(static_cast(obj), Fake(i)); i++; } diff --git a/implementation/local_array_method_multidimensional_test.cc b/implementation/local_array_method_multidimensional_test.cc index f10f97dd..21671b6a 100644 --- a/implementation/local_array_method_multidimensional_test.cc +++ b/implementation/local_array_method_multidimensional_test.cc @@ -48,7 +48,7 @@ TEST_F(JniTest, V_2I) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("I"), StrEq("()[[I"))); LocalObject obj{Fake()}; - obj("I"); + obj.Call<"I">(); } TEST_F(JniTest, V_2LkClass) { @@ -58,7 +58,7 @@ TEST_F(JniTest, V_2LkClass) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("()[[LkClass2;"))); LocalObject obj{Fake()}; - obj("Foo"); + obj.Call<"Foo">(); } //////////////////////////////////////////////////////////////////////////////// @@ -77,7 +77,7 @@ TEST_F(JniTest, 3I1D_2LkClass) { GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); LocalObject obj{Fake()}; - obj("Bar", Fake(), Fake()); + obj.Call<"Bar">(Fake(), Fake()); } TEST_F(JniTest, 2I_I) { @@ -88,8 +88,8 @@ TEST_F(JniTest, 2I_I) { LocalObject obj{Fake()}; // obj("I", jintArray{nullptr}); // doesn't compile (good). - obj("I", Fake()); - obj("I", LocalArray{Fake()}); + obj.Call<"I">(Fake()); + obj.Call<"I">(LocalArray{Fake()}); } TEST_F(JniTest, 3I_1LkClass) { @@ -103,8 +103,7 @@ TEST_F(JniTest, 3I_1LkClass) { // TODO(b/143908983): CTAD is failing. // LocalArray ret = obj("Baz", jobjectArray{nullptr}); - - LocalArray ret = obj("Baz", Fake()); + LocalArray ret = obj.Call<"Baz">(Fake()); static_assert(std::is_same_v>); } @@ -113,7 +112,7 @@ TEST_F(JniTest, 3I1D_1LkClass) { GetMethodID(_, StrEq("Bar"), StrEq("([[[I[D)[[LkClass3;"))); LocalObject obj{Fake()}; - obj("Bar", Fake(), Fake()); + obj.Call<"Bar">(Fake(), Fake()); } } // namespace diff --git a/implementation/local_array_method_test.cc b/implementation/local_array_method_test.cc index 24e27609..e9d87cef 100644 --- a/implementation/local_array_method_test.cc +++ b/implementation/local_array_method_test.cc @@ -78,31 +78,31 @@ TEST_F(JniTest, ReturnSmokeTest) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("ObjectArray"), StrEq("()[LkClass2;"))); - LocalArray bool_array{obj("BooleanArray")}; + LocalArray bool_array{obj.Call<"BooleanArray">()}; EXPECT_EQ((static_cast(bool_array)), (Fake())); - LocalArray byte_array{obj("ByteArray")}; + LocalArray byte_array{obj.Call<"ByteArray">()}; EXPECT_EQ((static_cast(byte_array)), (Fake())); - LocalArray char_array{obj("CharArray")}; + LocalArray char_array{obj.Call<"CharArray">()}; EXPECT_EQ((static_cast(char_array)), (Fake())); - LocalArray short_array{obj("ShortArray")}; + LocalArray short_array{obj.Call<"ShortArray">()}; EXPECT_EQ((static_cast(short_array)), (Fake())); - LocalArray int_array{obj("IntArray")}; + LocalArray int_array{obj.Call<"IntArray">()}; EXPECT_EQ((static_cast(int_array)), (Fake())); - LocalArray long_array{obj("LongArray")}; + LocalArray long_array{obj.Call<"LongArray">()}; EXPECT_EQ((static_cast(long_array)), (Fake())); - LocalArray float_array{obj("FloatArray")}; + LocalArray float_array{obj.Call<"FloatArray">()}; EXPECT_EQ((static_cast(float_array)), (Fake())); - LocalArray double_array{obj("DoubleArray")}; + LocalArray double_array{obj.Call<"DoubleArray">()}; EXPECT_EQ((static_cast(double_array)), (Fake())); - LocalArray object_array{obj("ObjectArray")}; + LocalArray object_array{obj.Call<"ObjectArray">()}; EXPECT_EQ((static_cast(object_array)), (Fake())); } @@ -135,15 +135,16 @@ TEST_F(JniTest, ParamsSmokeTest) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("ObjectArray"), StrEq("([LkClass2;)V"))); - obj("BooleanArray", LocalArray{Fake()}); - obj("ByteArray", LocalArray{Fake()}); - obj("CharArray", LocalArray{Fake()}); - obj("ShortArray", LocalArray{Fake()}); - obj("IntArray", LocalArray{Fake()}); - obj("FloatArray", LocalArray{Fake()}); - obj("DoubleArray", LocalArray{Fake()}); - obj("LongArray", LocalArray{Fake()}); - obj("ObjectArray", LocalArray{Fake()}); + obj.Call<"BooleanArray">(LocalArray{Fake()}); + obj.Call<"ByteArray">(LocalArray{Fake()}); + obj.Call<"CharArray">(LocalArray{Fake()}); + obj.Call<"ShortArray">(LocalArray{Fake()}); + obj.Call<"IntArray">(LocalArray{Fake()}); + obj.Call<"FloatArray">(LocalArray{Fake()}); + obj.Call<"DoubleArray">(LocalArray{Fake()}); + obj.Call<"LongArray">(LocalArray{Fake()}); + obj.Call<"ObjectArray">( + LocalArray{Fake()}); } //////////////////////////////////////////////////////////////////////////////// @@ -189,35 +190,35 @@ TEST_F(JniTest, ComplexSmokeTest) { StrEq("([LkClass2;)[LkClass2;"))); LocalArray bool_array{ - obj("BooleanArray", LocalArray{Fake()})}; + obj.Call<"BooleanArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(bool_array)), (Fake())); LocalArray byte_array{ - obj("ByteArray", LocalArray{Fake()})}; + obj.Call<"ByteArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(byte_array)), (Fake())); LocalArray char_array{ - obj("CharArray", LocalArray{Fake()})}; + obj.Call<"CharArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(char_array)), (Fake())); LocalArray short_array{ - obj("ShortArray", LocalArray{Fake()})}; + obj.Call<"ShortArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(short_array)), (Fake())); LocalArray int_array{ - obj("IntArray", LocalArray{Fake()})}; + obj.Call<"IntArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(int_array)), (Fake())); LocalArray float_array{ - obj("FloatArray", LocalArray{Fake()})}; + obj.Call<"FloatArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(float_array)), (Fake())); LocalArray double_array{ - obj("DoubleArray", LocalArray{Fake()})}; + obj.Call<"DoubleArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(double_array)), (Fake())); LocalArray long_array{ - obj("LongArray", LocalArray{Fake()})}; + obj.Call<"LongArray">(LocalArray{Fake()})}; EXPECT_EQ((static_cast(long_array)), (Fake())); LocalArray object_array{obj( diff --git a/implementation/local_array_string_test.cc b/implementation/local_array_string_test.cc index b0990e20..3bcf7d21 100644 --- a/implementation/local_array_string_test.cc +++ b/implementation/local_array_string_test.cc @@ -72,7 +72,7 @@ TEST_F(JniTest, Array_CorrectSignatureForStringParams) { LocalObject obj{jobject{nullptr}}; LocalArray arr{3}; - obj("StringArray", arr); + obj.Call<"StringArray">(arr); } //////////////////////////////////////////////////////////////////////////////// @@ -128,7 +128,7 @@ TEST_F(JniTest, Array_CorrectReturnSignatureForStrings) { LocalObject obj{Fake()}; EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), StrEq("()[Ljava/lang/String;"))); - LocalArray arr = obj("StringArray"); + LocalArray arr = obj.Call<"StringArray">(); } //////////////////////////////////////////////////////////////////////////////// @@ -144,7 +144,7 @@ TEST_F(JniTest, Array_CorrectParamSignatureForStrings) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("StringArray"), StrEq("([Ljava/lang/String;)V"))); LocalArray arr{2}; - obj("StringArray", arr); + obj.Call<"StringArray">(arr); } TEST_F(JniTest, LocalStringArray_ConstructsObjectsForLValues) { @@ -196,9 +196,9 @@ TEST_F(JniTest, Array_CorrectFieldSignatureForStrings) { EXPECT_CALL(*env_, GetFieldID(_, StrEq("StringArrayRank3"), StrEq("[[[Ljava/lang/String;"))); - LocalArray arr1 = obj["StringArrayRank1"].Get(); - LocalArray arr2 = obj["StringArrayRank2"].Get(); - LocalArray arr3 = obj["StringArrayRank3"].Get(); + LocalArray arr1 = obj.Access<"StringArrayRank1">().Get(); + LocalArray arr2 = obj.Access<"StringArrayRank2">().Get(); + LocalArray arr3 = obj.Access<"StringArrayRank3">().Get(); } } // namespace diff --git a/implementation/local_object_test.cc b/implementation/local_object_test.cc index ef193ac3..87c1af07 100644 --- a/implementation/local_object_test.cc +++ b/implementation/local_object_test.cc @@ -124,9 +124,10 @@ TEST_F(JniTest, LocalObject_ObjectReturnsInstanceMethods) { EXPECT_CALL(*env_, DeleteLocalRef(Fake())).Times(1); LocalObject obj{}; - obj("Foo", 12345); - obj("Baz", 12345.f); - obj("AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle", + obj.Call<"Foo">(12345); + obj.Call<"Baz">(12345.f); + obj.Call< + "AMethodWithAReallyLongNameThatWouldPossiblyBeHardForTemplatesToHandle">( 12345, 12345.f, 12345, 12345.f, jdouble{12345}); } @@ -164,15 +165,15 @@ TEST_F(JniTest, LocalObject_ValuesWorkAfterMoveConstructor) { EXPECT_CALL(*env_, SetIntField).Times(4); LocalObject obj_1{Fake()}; - obj_1("Foo", 1); - obj_1("Foo", 2); - obj_1["BarField"].Set(1); + obj_1.Call<"Foo">(1); + obj_1.Call<"Foo">(2); + obj_1.Access<"BarField">().Set(1); LocalObject obj_2{std::move(obj_1)}; - obj_2("Foo", 3); + obj_2.Call<"Foo">(3); obj_2["BarField"].Set(2); - obj_2["BarField"].Set(3); - obj_2["BarField"].Set(4); + obj_2.Access<"BarField">().Set(3); + obj_2.Access<"BarField">().Set(4); } TEST_F(JniTest, LocalObject_ReleasesLocalsForAlternateConstructors) { @@ -249,7 +250,7 @@ TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnLvalue) { LocalObject a{}; LocalObject b{}; - b("Foo", a); + b.Call<"Foo">(a); } TEST_F(JniTest, LocalObject_SupportsReturningAClass) { @@ -257,7 +258,7 @@ TEST_F(JniTest, LocalObject_SupportsReturningAClass) { "Class1", Method{"Foo", jni::Return{kClass2}, jni::Params{}}}; LocalObject a{}; - a("Foo"); + a.Call<"Foo">(); } TEST_F(JniTest, LocalObject_SupportsReturningAString) { @@ -265,13 +266,13 @@ TEST_F(JniTest, LocalObject_SupportsReturningAString) { "Class1", Method{"Foo", jni::Return{}, jni::Params{}}}; LocalObject a{}; - a("Foo"); + a.Call<"Foo">(); } jobject ReturnOutputOfMethod() { static constexpr Class kClass2{"Class2", Method{"Foo", jni::Return{kClass}}}; - return LocalObject{}("Foo").Release(); + return LocalObject{}.Call<"Foo">().Release(); } TEST_F(JniTest, LocalObject_CompilesWhenReturnReleasing) { @@ -284,7 +285,7 @@ TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnPrvalue) { LocalObject a{}; LocalObject b{}; - b("Foo", std::move(a)); + b.Call<"Foo">(std::move(a)); } TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnXvalue) { @@ -292,7 +293,7 @@ TEST_F(JniTest, LocalObject_SupportsPassingAnObjectAsAnXvalue) { "Class2", Method{"Foo", jni::Return{}, jni::Params{kClass}}}; LocalObject b{}; - b("Foo", LocalObject{}); + b.Call<"Foo">(LocalObject{}); } TEST_F(JniTest, LocalObject_MovesInContainerStruct) { @@ -320,8 +321,8 @@ TEST_F(JniTest, LocalObject_DoesntCrossTalkOverClassMethodIds) { LocalObject obj_2{Fake(2)}; // These are different method IDs (they are different classes). - obj_1("Foo", 1); - obj_2("Foo", 1); + obj_1.Call<"Foo">(1); + obj_2.Call<"Foo">(1); } } // namespace diff --git a/implementation/method_ref_test.cc b/implementation/method_ref_test.cc index 65c907ef..3406428a 100644 --- a/implementation/method_ref_test.cc +++ b/implementation/method_ref_test.cc @@ -193,7 +193,7 @@ TEST_F(JniTest, MethodRef_ReturnsObjects) { EXPECT_CALL(*env_, NewObjectV).WillOnce(testing::Return(Fake())); GlobalObject global_object{}; - LocalObject new_obj{global_object("Foo", 5)}; + LocalObject new_obj{global_object.Call<"Foo">(5)}; } TEST_F(JniTest, MethodRef_PassesObjects) { @@ -209,7 +209,7 @@ TEST_F(JniTest, MethodRef_PassesObjects) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(Lcom/google/Bazz;)I"))); - global_object("Foo", local_object); + global_object.Call<"Foo">(local_object); } TEST_F(JniTest, MethodRef_PassesAndReturnsMultipleObjects) { @@ -229,7 +229,7 @@ TEST_F(JniTest, MethodRef_PassesAndReturnsMultipleObjects) { LocalObject obj4{Fake(4)}; LocalObject object_under_test{Fake(5)}; - LocalObject obj5{object_under_test("Foo", obj1, obj2, obj3, obj4)}; + LocalObject obj5{object_under_test.Call<"Foo">(obj1, obj2, obj3, obj4)}; } TEST_F(JniTest, MethodRef_SupportsForwardDefines) { @@ -255,20 +255,21 @@ TEST_F(JniTest, MethodRef_SupportsForwardDefines) { LocalObject c2_obj1{Fake(3)}; LocalObject c2_obj2{Fake(4)}; - c1_obj1("m1", c1_obj1); - c1_obj1("m2", c2_obj1); - c1_obj1("m1", c1_obj1("m3")); - c1_obj1("m2", c1_obj1("m4")); + c1_obj1.Call<"m1">(c1_obj1); + c1_obj1.Call<"m2">(c2_obj1); + c1_obj1.Call<"m1">(c1_obj1.Call<"m3">()); + c1_obj1.Call<"m2">(c1_obj1.Call<"m4">()); - c2_obj1("m1", c1_obj1); - c2_obj1("m2", c2_obj2); - c2_obj1("m2", std::move(std::move(c2_obj2))); + c2_obj1.Call<"m1">(c1_obj1); + c2_obj1.Call<"m2">(c2_obj2); + c2_obj1.Call<"m2">(std::move(std::move(c2_obj2))); - c1_obj1("m2", std::move(c2_obj1)); + c1_obj1.Call<"m2">(std::move(c2_obj1)); - // c2_obj1("m1", c1_obj1); // illegal! triggers warnings (post move read). - // c2_obj1("m2", c2_obj2); // illegal! triggers warnings (post move read). - // c2_obj1("m2", std::move(c2_obj2)); // illegal! triggers warnings (post + // c2_obj1.Call<"m1">(c1_obj1); // illegal! triggers warnings (post move + // read). c2_obj1.Call<"m2">(c2_obj2); // illegal! triggers warnings (post + // move read). c2_obj1.Call<"m2">(std::move(c2_obj2)); // illegal! triggers + // warnings (post // move read). } @@ -281,9 +282,9 @@ TEST_F(JniTest, MethodRef_SupportsStrings) { }; LocalObject obj1{Fake()}; - obj1("Foo", "This is a method."); - obj1("Bar", "This is a method.", "It takes strings"); - obj1("Baz"); + obj1.Call<"Foo">("This is a method."); + obj1.Call<"Bar">("This is a method.", "It takes strings"); + obj1.Call<"Baz">(); } TEST_F(JniTest, MethodRef_SupportsArrays) { @@ -295,7 +296,7 @@ TEST_F(JniTest, MethodRef_SupportsArrays) { LocalArray local_array{nullptr}; LocalObject obj1{Fake()}; - obj1("Foo", local_array); + obj1.Call<"Foo">(local_array); } } // namespace diff --git a/implementation/multi_type_test.cc b/implementation/multi_type_test.cc index 3aceb6b0..b926bdbb 100644 --- a/implementation/multi_type_test.cc +++ b/implementation/multi_type_test.cc @@ -71,13 +71,13 @@ TEST_F(JniTest, MultiTypeTest_SimpleSmokeTestForSingleObject) { EXPECT_CALL(*env_, DeleteGlobalRef(Fake(2))).Times(1); jni::GlobalObject obj{}; - obj("Foo", 1, 2.f); - obj("Baz", 1.f); - obj("Baz", 1.f); - obj("Baz", 2.f); - obj("Baz", 3.f); - obj("Bar"); - obj["SomeField"].Get(); + obj.Call<"Foo">(1, 2.f); + obj.Call<"Baz">(1.f); + obj.Call<"Baz">(1.f); + obj.Call<"Baz">(2.f); + obj.Call<"Baz">(3.f); + obj.Call<"Bar">(); + obj.Access<"SomeField">().Get(); } TEST_F(JniTest, MultiTypeTest_MethodsOfSameNameButDifferentClassAreUnique) { @@ -94,21 +94,21 @@ TEST_F(JniTest, MultiTypeTest_MethodsOfSameNameButDifferentClassAreUnique) { jni::LocalObject obj1{Fake(1)}; jni::LocalObject obj2{Fake(2)}; - obj1("Foo", 12345); - obj2("Foo", 12345); + obj1.Call<"Foo">(12345); + obj2.Call<"Foo">(12345); // All of these calls ought not query for a method ID again. - obj1("Foo", 12345); - obj1("Foo", 12345); - obj1("Foo", 12345); - obj1("Foo", 12345); - obj2("Foo", 12345); - obj2("Foo", 12345); - obj2("Foo", 12345); + obj1.Call<"Foo">(12345); + obj1.Call<"Foo">(12345); + obj1.Call<"Foo">(12345); + obj1.Call<"Foo">(12345); + obj2.Call<"Foo">(12345); + obj2.Call<"Foo">(12345); + obj2.Call<"Foo">(12345); jni::LocalObject obj3{Fake(3)}; - obj3("Foo", 12345); - obj3("Foo", 12345); + obj3.Call<"Foo">(12345); + obj3.Call<"Foo">(12345); } } // namespace diff --git a/implementation/object_ref.h b/implementation/object_ref.h index e3fef711..420ad6f9 100644 --- a/implementation/object_ref.h +++ b/implementation/object_ref.h @@ -34,8 +34,11 @@ #include "implementation/ref_base.h" #include "jni_dep.h" #include "metaprogramming/invocable_map.h" +#include "metaprogramming/invocable_map_20.h" #include "metaprogramming/queryable_map.h" +#include "metaprogramming/queryable_map_20.h" #include "metaprogramming/string_contains.h" +#include "metaprogramming/string_literal.h" namespace jni { @@ -47,11 +50,17 @@ namespace jni { // operator[]. template class ObjectRef + // C++17 augmentations. : public metaprogramming::InvocableMap< ObjectRef, JniT::stripped_class_v, typename JniT::ClassT, &JniT::ClassT::methods_>, public metaprogramming::QueryableMap_t< ObjectRef, JniT::stripped_class_v, &JniT::ClassT::fields_>, + // C++ 20 augmentations. + public metaprogramming::InvocableMap20_t< + ObjectRef, JniT::stripped_class_v, &JniT::ClassT::methods_>, + public metaprogramming::QueryableMap20_t< + ObjectRef, JniT::stripped_class_v, &JniT::ClassT::fields_>, public RefBase { protected: static_assert( @@ -83,6 +92,12 @@ class ObjectRef explicit ObjectRef(RefBaseT&& rhs) : RefBaseT(std::move(rhs)) {} + //////////////////////////////////////////////////////////////////////////////// + // Implementation: C++17 + clang + // Supports syntax like: "obj("foo", 123, args)", "obj["foo"].Get()" + // This syntax is less portable and may be removed in a major future release. + //////////////////////////////////////////////////////////////////////////////// +#if __clang__ // Invoked through CRTP from InvocableMap. template auto InvocableMapCall(const char* key, Args&&... args) const { @@ -103,10 +118,41 @@ class ObjectRef auto QueryableMapCall(const char* key) const { return FieldRef{GetJClass(), RefBaseT::object_ref_}; } +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// C++20 Implementation (will only be included if supported). +// Supports syntax like: "obj.Call<"foo">(123, args)", +// "obj.Access<"foo">().Get()" Prefer using this syntax as they are more +// portable. +//////////////////////////////////////////////////////////////////////////////// +#if __cplusplus >= 202002L + // Invoked through CRTP from InvocableMap, C++20 only. + template + auto InvocableMap20Call(Args&&... args) const { + using IdT = Id; + using MethodSelectionForArgs = + OverloadSelector; + + static_assert(MethodSelectionForArgs::kIsValidArgSet, + "JNI Error: Invalid argument set."); + + return MethodSelectionForArgs::OverloadRef::Invoke( + GetJClass(), RefBaseT::object_ref_, std::forward(args)...); + } + + // Invoked through CRTP from QueryableMap20, C++20 only. + template + auto QueryableMap20Call() const { + return FieldRef{GetJClass(), RefBaseT::object_ref_}; + } +#endif // __cplusplus >= 202002L }; // Imbues constructors for ObjectRefs and handles calling the correct -// intermediate constructors. Access to this class is constrainted for non +// intermediate constructors. Access to this class is constrained for non // default classloaders (see |ValidatorProxy|). template class ConstructorValidator : public ObjectRef { diff --git a/implementation/overload_ref_test.cc b/implementation/overload_ref_test.cc index b42e71e6..d452b9b3 100644 --- a/implementation/overload_ref_test.cc +++ b/implementation/overload_ref_test.cc @@ -40,7 +40,7 @@ TEST_F(JniTest, MethodRef_AsksForCorrectMethods1) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); - obj("Foo", "test"); + obj.Call<"Foo">("test"); } TEST_F(JniTest, MethodRef_AsksForCorrectMethods2) { @@ -54,9 +54,9 @@ TEST_F(JniTest, MethodRef_AsksForCorrectMethods2) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;)V"))); - obj("Foo", 1); - // obj("Foo", 2.f); doesn't compile (good). - obj("Foo", "test"); + obj.Call<"Foo">(1); + // obj.Call<"Foo">(2.f); doesn't compile (good). + obj.Call<"Foo">("test"); } TEST_F(JniTest, MethodRef_AsksForCorrectMethods3) { @@ -77,11 +77,11 @@ TEST_F(JniTest, MethodRef_AsksForCorrectMethods3) { GetMethodID(_, StrEq("Foo"), StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); - obj("Foo", 1); - // obj("Foo", 2.f); // doesn't compile (good). - obj("Foo", "test"); - obj("Foo", "test1", "test2"); - obj("Foo", "this_doesnt", "trigger_method_lookup"); + obj.Call<"Foo">(1); + // obj.Call<"Foo">(2.f); // doesn't compile (good). + obj.Call<"Foo">("test"); + obj.Call<"Foo">("test1", "test2"); + obj.Call<"Foo">("this_doesnt", "trigger_method_lookup"); } TEST_F(JniTest, MethodRef_AsksForCorrectMethodsWhenMultiplePresent4) { @@ -113,14 +113,14 @@ TEST_F(JniTest, MethodRef_AsksForCorrectMethodsWhenMultiplePresent4) { StrEq("(Ljava/lang/String;Ljava/lang/String;)V"))); EXPECT_CALL(*env_, GetMethodID(_, StrEq("Baz"), StrEq("(FF)V"))); - obj("Foo", 1); - // obj("Foo", 2.f); // doesn't compile (good). - obj("Foo", "test"); - obj("Foo", "test1", "test2"); - obj("Foo", "this_doesnt", "trigger_method_lookup"); - obj("Baz", 1); - obj("Baz", "test3", "test4"); - obj("Baz", 1234.f, 5678.f); + obj.Call<"Foo">(1); + // obj.Call<"Foo">(2.f); // doesn't compile (good). + obj.Call<"Foo">("test"); + obj.Call<"Foo">("test1", "test2"); + obj.Call<"Foo">("this_doesnt", "trigger_method_lookup"); + obj.Call<"Baz">(1); + obj.Call<"Baz">("test3", "test4"); + obj.Call<"Baz">(1234.f, 5678.f); } } // namespace diff --git a/implementation/proxy_test.cc b/implementation/proxy_test.cc index f82ed8b6..3a9a15e5 100644 --- a/implementation/proxy_test.cc +++ b/implementation/proxy_test.cc @@ -23,6 +23,7 @@ #include #include "jni_bind.h" #include "jni_test.h" +#include "metaprogramming/type_to_type_map.h" using ::jni::AsDecl_t; using ::jni::Class; @@ -190,28 +191,28 @@ TEST_F(JniTest, MaterializationTests) { // Objects can be rvalues (here an x-value). LocalObject generator_obj{}; - LocalObject local_1{generator_obj("ReturnsObj")}; + LocalObject local_1{generator_obj.Call<"ReturnsObj">()}; // Objects can also be described with no actual class definition. // These objects won't be usable, but they are "name-safe". - LocalObject local_2{generator_obj("ReturnsObj")}; + LocalObject local_2{generator_obj.Call<"ReturnsObj">()}; // doesn't compile because of invalid class (good). - // LocalObject local_3 { generator_obj("ReturnsObj") }; } + // LocalObject local_3 { generator_obj.Call<"ReturnsObj">() }; } // Globals can be built from locals. - GlobalObject global_1{generator_obj("ReturnsObj")}; - global_1("Bar"); + GlobalObject global_1{generator_obj.Call<"ReturnsObj">()}; + global_1.Call<"Bar">(); // Globals can be built from expiring locals without a full type. - GlobalObject global_2{generator_obj("ReturnsObj")}; + GlobalObject global_2{generator_obj.Call<"ReturnsObj">()}; // But they lack sufficient definition to have any invocation! - // global_2("Bar"); + // global_2.Call<"Bar">(); // They can be promoted however, and thus restore their type info. GlobalObject global_3{std::move(global_2)}; - global_3("Bar"); + global_3.Call<"Bar">(); } } // namespace jni diff --git a/implementation/return.h b/implementation/return.h index 8f352be3..7b6e8f57 100644 --- a/implementation/return.h +++ b/implementation/return.h @@ -31,7 +31,7 @@ struct Return : ReturnBase { using Raw = Raw_; - constexpr Return() = default; + constexpr Return() {} template constexpr explicit Return(Raw raw) : raw_(raw) {} @@ -42,7 +42,7 @@ struct Return : ReturnBase { using Raw = void; const Void raw_{}; - constexpr Return() = default; + constexpr Return() {} }; Return() -> Return; diff --git a/implementation/self_test.cc b/implementation/self_test.cc index 68e81d25..d2fd2e81 100644 --- a/implementation/self_test.cc +++ b/implementation/self_test.cc @@ -44,7 +44,7 @@ TEST_F(JniTest, SelfCanBeUsedAsAReturnValue) { StrEq("()LBuilder;"))); LocalObject obj{Fake()}; - obj("build"); + obj.Call<"build">(); } TEST_F(JniTest, SelfCanBeUsedAsAReturnValueAndMaintainsRichDecoration) { @@ -52,7 +52,7 @@ TEST_F(JniTest, SelfCanBeUsedAsAReturnValueAndMaintainsRichDecoration) { EXPECT_CALL(*env_, GetMethodID(_, StrEq("build"), StrEq("()LBuilder;"))); LocalObject obj{Fake()}; - obj("build")("build")("build"); + obj.Call<"build">().Call<"build">().Call<"build">(); } TEST_F(JniTest, SelfCanBeUsedAsParam) { @@ -62,7 +62,7 @@ TEST_F(JniTest, SelfCanBeUsedAsParam) { LocalObject obj_1{Fake(1)}; LocalObject obj_2{Fake(2)}; - obj_1("takesBuilder", obj_2); + obj_1.Call<"takesBuilder">(obj_2); } } // namespace diff --git a/implementation/static_ref.h b/implementation/static_ref.h index ff5ec6b0..3aa22f82 100644 --- a/implementation/static_ref.h +++ b/implementation/static_ref.h @@ -32,7 +32,10 @@ #include "implementation/no_idx.h" #include "jni_dep.h" #include "metaprogramming/invocable_map.h" +#include "metaprogramming/invocable_map_20.h" #include "metaprogramming/queryable_map.h" +#include "metaprogramming/queryable_map_20.h" +#include "metaprogramming/string_literal.h" namespace jni { @@ -41,13 +44,23 @@ template ; + // C++17 augmentations. using MethodMapT = metaprogramming::InvocableMap; using FieldMapT = metaprogramming::QueryableMap_t; + + // C++ 20 augmentations. + using MethodMap20T = + metaprogramming::InvocableMap20_t; + using FieldMap20T = + metaprogramming::QueryableMap20_t; }; +// C++17 augmentations. template using StaticRefHelperMethodMap_t = @@ -60,21 +73,45 @@ using StaticRefHelperFieldMap_t = typename StaticRefHelper::FieldMapT; +// C++20 augmentations. +template +using StaticRefHelperMethodMap20_t = + typename StaticRefHelper::MethodMap20T; +template +using StaticRefHelperFieldMap20_t = + typename StaticRefHelper::FieldMap20T; + template struct StaticRef - : public StaticRefHelperMethodMap_t< - StaticRef, class_v_, - class_loader_v_, jvm_v_>, + : public + // C++17 augmentations. + StaticRefHelperMethodMap_t, + class_v_, class_loader_v_, jvm_v_>, StaticRefHelperFieldMap_t, - class_v_, class_loader_v_, jvm_v_> { + class_v_, class_loader_v_, jvm_v_>, + // C++ 20 augmentations. + StaticRefHelperMethodMap20_t, + class_v_, class_loader_v_, jvm_v_>, + StaticRefHelperFieldMap20_t, + class_v_, class_loader_v_, jvm_v_> { using JniT = JniT; jclass GetJClass() const { return ClassRef_t::GetAndMaybeLoadClassRef(nullptr); } + //////////////////////////////////////////////////////////////////////////////// + // Implementation: C++17 + clang + // Supports syntax like: "obj("foo", 123, args)", "obj["foo"].Get()" + // This syntax is less portable and may be removed in a major future release. + //////////////////////////////////////////////////////////////////////////////// +#if __clang__ template auto InvocableMapCall(const char* key, Args&&... args) const { using IdT = Id; @@ -93,6 +130,31 @@ struct StaticRef auto QueryableMapCall(const char* key) const { return FieldRef{GetJClass(), nullptr}; } +#endif // __clang__ + +#if __cplusplus >= 202002L + // Invoked through CRTP from InvocableMap, C++20 only. + template + auto InvocableMap20Call(Args&&... args) const { + using IdT = Id; + using MethodSelectionForArgs = + OverloadSelector; + + static_assert(MethodSelectionForArgs::kIsValidArgSet, + "JNI Error: Invalid argument set."); + + return MethodSelectionForArgs::OverloadRef::Invoke( + GetJClass(), nullptr, std::forward(args)...); + } + + // Invoked through CRTP from QueryableMap20, C++20 only. + template + auto QueryableMap20Call() const { + return FieldRef{GetJClass(), nullptr}; + } +#endif // __cplusplus >= 202002L }; } // namespace jni diff --git a/implementation/static_ref_test.cc b/implementation/static_ref_test.cc index 2a560458..9938f89b 100644 --- a/implementation/static_ref_test.cc +++ b/implementation/static_ref_test.cc @@ -73,7 +73,7 @@ TEST_F(JniTest, StaticBooleanField) { EXPECT_CALL(*env_, SetStaticBooleanField(_, Fake(), true)); LocalObject obj; - EXPECT_TRUE(StaticRef{}["booleanField"].Get()); + EXPECT_TRUE(StaticRef{}.Access<"booleanField">().Get()); StaticRef{}["booleanField"].Set(true); } @@ -276,21 +276,21 @@ TEST_F(JniTest, StaticExerciseAllReturns) { EXPECT_CALL(*env_, CallStaticObjectMethodV).Times(5); - StaticRef{}("booleanMethod"); - StaticRef{}("byteMethod"); - StaticRef{}("charMethod"); - StaticRef{}("shortMethod"); - StaticRef{}("intMethod"); - StaticRef{}("longMethod"); - StaticRef{}("floatMethod"); - StaticRef{}("doubleMethod"); - StaticRef{}("stringMethod"); - - StaticRef{}("objectMethod"); - StaticRef{}("rank1ArrayMethod"); - StaticRef{}("rank2ArrayMethod"); - - LocalObject self_ret = StaticRef{}("selfMethod"); + StaticRef{}.Call<"booleanMethod">(); + StaticRef{}.Call<"byteMethod">(); + StaticRef{}.Call<"charMethod">(); + StaticRef{}.Call<"shortMethod">(); + StaticRef{}.Call<"intMethod">(); + StaticRef{}.Call<"longMethod">(); + StaticRef{}.Call<"floatMethod">(); + StaticRef{}.Call<"doubleMethod">(); + StaticRef{}.Call<"stringMethod">(); + + StaticRef{}.Call<"objectMethod">(); + StaticRef{}.Call<"rank1ArrayMethod">(); + StaticRef{}.Call<"rank2ArrayMethod">(); + + LocalObject self_ret = StaticRef{}.Call<"selfMethod">(); } // clang-format off @@ -339,23 +339,23 @@ TEST_F(JniTest, StaticExerciseAllTypesThroughSingleParam) { EXPECT_CALL(*env_, CallStaticVoidMethodV).Times(13); - StaticRef{}("booleanMethod", jboolean{true}); - StaticRef{}("byteMethod", jbyte{1}); - StaticRef{}("charMethod", jchar{'a'}); - StaticRef{}("shortMethod", jshort{1}); - StaticRef{}("intMethod", jint{123}); - StaticRef{}("longMethod", jlong{456}); - StaticRef{}("floatMethod", jfloat{789.f}); - StaticRef{}("doubleMethod", jdouble{101.}); - StaticRef{}("stringMethod", "test"); + StaticRef{}.Call<"booleanMethod">(jboolean{true}); + StaticRef{}.Call<"byteMethod">(jbyte{1}); + StaticRef{}.Call<"charMethod">(jchar{'a'}); + StaticRef{}.Call<"shortMethod">(jshort{1}); + StaticRef{}.Call<"intMethod">(jint{123}); + StaticRef{}.Call<"longMethod">(jlong{456}); + StaticRef{}.Call<"floatMethod">(jfloat{789.f}); + StaticRef{}.Call<"doubleMethod">(jdouble{101.}); + StaticRef{}.Call<"stringMethod">("test"); // It would be more complete to exercise all types here. - StaticRef{}("objectMethod", Fake()); - StaticRef{}("rank1ArrayMethod", - Fake()); - StaticRef{}("rank2ArrayMethod", - Fake()); - StaticRef{}("selfMethod", Fake()); + StaticRef{}.Call<"objectMethod">(Fake()); + StaticRef{}.Call<"rank1ArrayMethod">( + Fake()); + StaticRef{}.Call<"rank2ArrayMethod">( + Fake()); + StaticRef{}.Call<"selfMethod">(Fake()); } TEST_F(JniTest, StaticExerciseComplexSetOfParams) { @@ -365,9 +365,9 @@ TEST_F(JniTest, StaticExerciseComplexSetOfParams) { EXPECT_CALL(*env_, GetStaticMethodID(_, StrEq("complexFunc"), StrEq("([[LkClass2;IFLkClass3;)F"))); - StaticRef{}("simpleFunc", 123.f); - StaticRef{}("complexFunc", jobjectArray{nullptr}, 123, 456.f, - jobject{nullptr}); + StaticRef{}.Call<"simpleFunc">(123.f); + StaticRef{}.Call<"complexFunc">(jobjectArray{nullptr}, 123, + 456.f, jobject{nullptr}); } } // namespace diff --git a/implementation/string_ref_test.cc b/implementation/string_ref_test.cc index 722def8f..debc55e3 100644 --- a/implementation/string_ref_test.cc +++ b/implementation/string_ref_test.cc @@ -14,6 +14,11 @@ * limitations under the License. */ +#include +#include +#include + +#include #include #include "implementation/jni_helper/fake_test_constants.h" #include "jni_bind.h" @@ -86,7 +91,7 @@ TEST_F(JniTest, LocalString_CopiesFromJString) { TEST_F(JniTest, LocalString_ConstructsFromOutputOfMethod) { LocalObject obj{}; - LocalString str{obj("Foo")}; + LocalString str{obj.Call<"Foo">()}; } TEST_F(JniTest, LocalString_ConstructsFromByteArray) { @@ -146,12 +151,12 @@ TEST_F(JniTest, LocalString_PinsAndUnpinsMemoryForLocals) { TEST_F(JniTest, LocalString_AllowsLValueLocalString) { LocalObject obj{}; LocalString local_string{"abcde"}; - obj("TakesStrParam", local_string); + obj.Call<"TakesStrParam">(local_string); } TEST_F(JniTest, LocalString_AllowsRValueLocalString) { LocalObject obj{}; - obj("TakesStrParam", LocalString{"abcde"}); + obj.Call<"TakesStrParam">(LocalString{"abcde"}); } //////////////////////////////////////////////////////////////////////////////// @@ -222,12 +227,12 @@ TEST_F(JniTest, GlobalString_PinsAndUnpinsMemoryForLocals) { TEST_F(JniTest, GlobalString_AllowsLValueGlobalString) { LocalObject obj{}; GlobalString global_string{"abcde"}; - obj("TakesStrParam", global_string); + obj.Call<"TakesStrParam">(global_string); } TEST_F(JniTest, GlobalString_AllowsRValueGlobalString) { LocalObject obj{}; - obj("TakesStrParam", GlobalString{"abcde"}); + obj.Call<"TakesStrParam">(GlobalString{"abcde"}); } } // namespace diff --git a/java/com/google/main.cc b/java/com/google/main.cc index de5ea5ea..df9fca2f 100644 --- a/java/com/google/main.cc +++ b/java/com/google/main.cc @@ -46,7 +46,7 @@ void RunIterationsToCompletion(jni::JvmRef *jvm_ref) { for (int i = 0; i < kNumIterations; ++i) { printf("Iteration %i: %s\n", i, - random_string("format").Pin().ToString().data()); + random_string.Call<"format">().Pin().ToString().data()); } } diff --git a/javatests/com/jnibind/android/array_test_jni.cc b/javatests/com/jnibind/android/array_test_jni.cc index e193bf68..864f8c6b 100644 --- a/javatests/com/jnibind/android/array_test_jni.cc +++ b/javatests/com/jnibind/android/array_test_jni.cc @@ -55,11 +55,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeBooleanTests( // Simple lvalue pass through works as expected. LocalArray local_arr{boolean_array}; - rjni_test_helper("rJniBooleanArray", false, local_arr); + rjni_test_helper.Call<"rJniBooleanArray">(false, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniBooleanArray", false, - LocalArray{boolean_array}); + rjni_test_helper.Call<"rJniBooleanArray">( + false, LocalArray{boolean_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeBooleanTests( array_view.ptr()[i] = true; } } - rjni_test_helper("rJniBooleanArray", true, new_array); + rjni_test_helper.Call<"rJniBooleanArray">(true, new_array); // You can pull the view multiple times. { @@ -78,7 +78,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeBooleanTests( array_view.ptr()[i] = true; } } - rjni_test_helper("rJniBooleanArray", true, new_array); + rjni_test_helper.Call<"rJniBooleanArray">(true, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeByteTests( @@ -87,10 +87,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeByteTests( // Simple lvalue pass through works as expected. LocalArray local_arr{byte_array}; - rjni_test_helper("rJniByteArray", jbyte{0}, local_arr); + rjni_test_helper.Call<"rJniByteArray">(jbyte{0}, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniByteArray", jbyte{0}, LocalArray{byte_array}); + rjni_test_helper.Call<"rJniByteArray">(jbyte{0}, + LocalArray{byte_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -100,7 +101,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeByteTests( array_view.ptr()[i] = static_cast(i); } } - rjni_test_helper("rJniByteArray", jbyte{0}, new_array); + rjni_test_helper.Call<"rJniByteArray">(jbyte{0}, new_array); // You can pull the view multiple times. { @@ -109,7 +110,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeByteTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniByteArray", jbyte{5}, new_array); + rjni_test_helper.Call<"rJniByteArray">(jbyte{5}, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeCharTests( @@ -118,10 +119,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeCharTests( // Simple lvalue pass through works as expected. LocalArray local_arr{char_array}; - rjni_test_helper("rJniCharArray", jchar{0}, local_arr); + rjni_test_helper.Call<"rJniCharArray">(jchar{0}, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniCharArray", jchar{0}, LocalArray{char_array}); + rjni_test_helper.Call<"rJniCharArray">(jchar{0}, + LocalArray{char_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -131,7 +133,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeCharTests( array_view.ptr()[i] = static_cast(i); } } - rjni_test_helper("rJniCharArray", jchar{0}, new_array); + rjni_test_helper.Call<"rJniCharArray">(jchar{0}, new_array); // You can pull the view multiple times. { @@ -140,7 +142,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeCharTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniCharArray", jchar{5}, new_array); + rjni_test_helper.Call<"rJniCharArray">(jchar{5}, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeShortTests( @@ -149,11 +151,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeShortTests( // Simple lvalue pass through works as expected. LocalArray local_arr{short_array}; - rjni_test_helper("rJniShortArray", jshort{0}, local_arr); + rjni_test_helper.Call<"rJniShortArray">(jshort{0}, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniShortArray", jshort{0}, - LocalArray{short_array}); + rjni_test_helper.Call<"rJniShortArray">(jshort{0}, + LocalArray{short_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -163,7 +165,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeShortTests( array_view.ptr()[i] = static_cast(i); } } - rjni_test_helper("rJniShortArray", jshort{0}, new_array); + rjni_test_helper.Call<"rJniShortArray">(jshort{0}, new_array); // You can pull the view multiple times. { @@ -172,7 +174,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeShortTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniShortArray", jshort{5}, new_array); + rjni_test_helper.Call<"rJniShortArray">(jshort{5}, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeIntTests( @@ -181,10 +183,10 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeIntTests( // Simple lvalue pass through works as expected. LocalArray local_arr{int_array}; - rjni_test_helper("rJniIntArray", 0, local_arr); + rjni_test_helper.Call<"rJniIntArray">(0, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniIntArray", 0, LocalArray{int_array}); + rjni_test_helper.Call<"rJniIntArray">(0, LocalArray{int_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -194,7 +196,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeIntTests( array_view.ptr()[i] = i; } } - rjni_test_helper("rJniIntArray", 0, new_array); + rjni_test_helper.Call<"rJniIntArray">(0, new_array); // You can pull the view multiple times. { @@ -203,7 +205,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeIntTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniIntArray", 5, new_array); + rjni_test_helper.Call<"rJniIntArray">(5, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeLongTests( @@ -212,10 +214,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeLongTests( // Simple lvalue pass through works as expected. LocalArray local_arr{long_array}; - rjni_test_helper("rJniLongArray", jlong{0}, local_arr); + rjni_test_helper.Call<"rJniLongArray">(jlong{0}, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniLongArray", jlong{0}, LocalArray{long_array}); + rjni_test_helper.Call<"rJniLongArray">(jlong{0}, + LocalArray{long_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -225,7 +228,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeLongTests( array_view.ptr()[i] = static_cast(i); } } - rjni_test_helper("rJniLongArray", jlong{0}, new_array); + rjni_test_helper.Call<"rJniLongArray">(jlong{0}, new_array); // You can pull the view multiple times. { @@ -234,7 +237,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeLongTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniLongArray", jlong{5}, new_array); + rjni_test_helper.Call<"rJniLongArray">(jlong{5}, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeFloatTests( @@ -242,7 +245,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeFloatTests( LocalObject rjni_test_helper{test_fixture}; LocalArray local_arr{float_array}; - rjni_test_helper("rJniFloatArray", 0.f, local_arr); + rjni_test_helper.Call<"rJniFloatArray">(0.f, local_arr); { ArrayView array_view{local_arr.Pin(true)}; @@ -250,7 +253,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeFloatTests( array_view.ptr()[i] += 2.5f; } } - rjni_test_helper("rJniFloatArray", 2.5f, local_arr); + rjni_test_helper.Call<"rJniFloatArray">(2.5f, local_arr); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeDoubleTests( @@ -259,11 +262,11 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeDoubleTests( // Simple lvalue pass through works as expected. LocalArray local_arr{double_array}; - rjni_test_helper("rJniDoubleArray", jdouble{0}, local_arr); + rjni_test_helper.Call<"rJniDoubleArray">(jdouble{0}, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniDoubleArray", jdouble{0}, - LocalArray{double_array}); + rjni_test_helper.Call<"rJniDoubleArray">(jdouble{0}, + LocalArray{double_array}); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -273,7 +276,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeDoubleTests( array_view.ptr()[i] = static_cast(i); } } - rjni_test_helper("rJniDoubleArray", jdouble{0}, new_array); + rjni_test_helper.Call<"rJniDoubleArray">(jdouble{0}, new_array); // You can pull the view multiple times. { @@ -282,7 +285,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeDoubleTests( array_view.ptr()[i] += 5; } } - rjni_test_helper("rJniDoubleArray", jdouble{5}, new_array); + rjni_test_helper.Call<"rJniDoubleArray">(jdouble{5}, new_array); } JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeObjectTests( @@ -291,12 +294,12 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeObjectTests( // Simple lvalue pass through works as expected. LocalArray local_arr{object_array}; - rjni_test_helper("rJniObjectArray", 0, local_arr); + rjni_test_helper.Call<"rJniObjectArray">(0, local_arr); // Simple rvalue pass through works as expected. - rjni_test_helper("rJniObjectArray", 5, - LocalArray{ - 1, LocalObject{5, 5, 5}}); + rjni_test_helper.Call<"rJniObjectArray">( + 5, LocalArray{ + 1, LocalObject{5, 5, 5}}); // Building a new array, and setting all the values by hand works. LocalObject obj{0, 0, 0}; @@ -307,17 +310,17 @@ JNIEXPORT void JNICALL Java_com_jnibind_android_ArrayTest_nativeObjectTests( i, LocalObject{jint{i}, jint{i}, jint{i}}); } } - rjni_test_helper("rJniObjectArray", 0, new_array); + rjni_test_helper.Call<"rJniObjectArray">(0, new_array); // You can pull the view multiple times. { for (int i = 0; i < new_array.Length(); ++i) { - new_array.Set(i, - LocalObject{2, 2, 2}( - "returnNewObjectWithFieldSetToSum", new_array.Get(i))); + new_array.Set( + i, LocalObject{2, 2, 2} + .Call<"returnNewObjectWithFieldSetToSum">(new_array.Get(i))); } } - rjni_test_helper("rJniObjectArray", 2, new_array); + rjni_test_helper.Call<"rJniObjectArray">(2, new_array); } } // extern "C" diff --git a/javatests/com/jnibind/android/class_loader_test_jni.cc b/javatests/com/jnibind/android/class_loader_test_jni.cc index 83f84975..fe6cfc0c 100644 --- a/javatests/com/jnibind/android/class_loader_test_jni.cc +++ b/javatests/com/jnibind/android/class_loader_test_jni.cc @@ -74,8 +74,8 @@ Java_com_jnibind_android_ClassLoaderTest_jniBuildNewObjectsFromClassLoader( LocalObject helper_obj = class_loader.BuildLocalObject(1, 2, 3); return LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", helper_obj) + kJvmWithCustomClassLoaderSupport>{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -85,8 +85,8 @@ Java_com_jnibind_android_ClassLoaderTest_jniBuildNewObjectsFromDefaultClassLoade GlobalClassLoader class_loader{PromoteToGlobal{}, jclass_loader_obj}; LocalObject helper_obj{2, 3, 4}; - return LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", helper_obj) + return LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -101,7 +101,7 @@ Java_com_jnibind_android_ClassLoaderTest_jniBuildsRemoteSubclassFromClassLoader( jint{value}); LocalObject - parent_obj{helper_obj("castToParent")}; + parent_obj{helper_obj.Call<"castToParent">()}; return parent_obj("getValue"); } diff --git a/javatests/com/jnibind/android/context_test_jni.cc b/javatests/com/jnibind/android/context_test_jni.cc index 6ae2de6e..420e3eb3 100644 --- a/javatests/com/jnibind/android/context_test_jni.cc +++ b/javatests/com/jnibind/android/context_test_jni.cc @@ -39,13 +39,14 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { } JNIEXPORT void JNICALL Java_com_jnibind_android_ContextTest_DoSetup(JNIEnv* env, - jclass) { + jclass) { jvm.reset(new jni::JvmRef{env}); } JNIEXPORT jlong JNICALL -Java_com_jnibind_android_ContextTest_CreateNativeContextObject(JNIEnv* env, jclass, - jint val) { +Java_com_jnibind_android_ContextTest_CreateNativeContextObject(JNIEnv* env, + jclass, + jint val) { auto* ctx_struct = new ContextStruct{GlobalObject{}}; ctx_struct->obj["intVal1"].Set(jint{val}); @@ -57,25 +58,28 @@ Java_com_jnibind_android_ContextTest_CreateNativeContextObjectSetValToSum( JNIEnv* env, jclass, jint val1, jint val2) { // Creates a temporary test helper, calls its member method, and releases the // returned object across the C API boundary (then destroys the temporary). - return jni::LocalObject{}( - "returnNewObjectWithFieldSetToSum", val1, val2) + return jni::LocalObject{} + .Call<"returnNewObjectWithFieldSetToSum">(val1, val2) .Release(); } -JNIEXPORT jobject JNICALL Java_com_jnibind_android_ContextTest_QueryNativeObject( - JNIEnv* env, jclass, void* ctx_void_ptr) { +JNIEXPORT jobject JNICALL +Java_com_jnibind_android_ContextTest_QueryNativeObject(JNIEnv* env, jclass, + void* ctx_void_ptr) { ContextStruct* ctx_struct = static_cast(ctx_void_ptr); return jobject{ctx_struct->obj}; } -JNIEXPORT jobject JNICALL Java_com_jnibind_android_ContextTest_ExtractNativeObject( - JNIEnv* env, jclass, void* ctx_void_ptr) { +JNIEXPORT jobject JNICALL +Java_com_jnibind_android_ContextTest_ExtractNativeObject(JNIEnv* env, jclass, + void* ctx_void_ptr) { ContextStruct* ctx_struct = static_cast(ctx_void_ptr); return ctx_struct->obj.Release(); } -JNIEXPORT void JNICALL Java_com_jnibind_android_ContextTest_DestroyNativeContext( - JNIEnv* env, jclass, void* ctx_void_ptr) { +JNIEXPORT void JNICALL +Java_com_jnibind_android_ContextTest_DestroyNativeContext(JNIEnv* env, jclass, + void* ctx_void_ptr) { delete static_cast(ctx_void_ptr); } diff --git a/javatests/com/jnibind/android/field_test_jni.cc b/javatests/com/jnibind/android/field_test_jni.cc index 0b7fcc0a..569b5672 100644 --- a/javatests/com/jnibind/android/field_test_jni.cc +++ b/javatests/com/jnibind/android/field_test_jni.cc @@ -42,22 +42,22 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { JNIEXPORT jint JNICALL Java_com_jnibind_android_FieldTest_jniIntField( JNIEnv* env, jclass, jobject object, jint val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["intField"].Set(jint{val}); - return rjni_test_helper["intField"].Get(); + rjni_test_helper.Access<"intField">().Set(jint{val}); + return rjni_test_helper.Access<"intField">().Get(); } JNIEXPORT jfloat JNICALL Java_com_jnibind_android_FieldTest_jniFloatField( JNIEnv* env, jclass, jobject object, jfloat val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["floatField"].Set(jfloat{val}); - return rjni_test_helper["floatField"].Get(); + rjni_test_helper.Access<"floatField">().Set(jfloat{val}); + return rjni_test_helper.Access<"floatField">().Get(); } JNIEXPORT jdouble JNICALL Java_com_jnibind_android_FieldTest_jniDoubleField( JNIEnv* env, jclass, jobject object, jdouble val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["doubleField"].Set(jdouble{val}); - return rjni_test_helper["doubleField"].Get(); + rjni_test_helper.Access<"doubleField">().Set(jdouble{val}); + return rjni_test_helper.Access<"doubleField">().Get(); } } // extern "C" diff --git a/javatests/com/jnibind/android/global_object_test_jni.cc b/javatests/com/jnibind/android/global_object_test_jni.cc index aa296ff1..6808682d 100644 --- a/javatests/com/jnibind/android/global_object_test_jni.cc +++ b/javatests/com/jnibind/android/global_object_test_jni.cc @@ -32,7 +32,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { } JNIEXPORT jobject JNICALL -Java_com_jnibind_android_GlobalObjectTest_jniCreateNewObject(JNIEnv* env, jclass) { +Java_com_jnibind_android_GlobalObjectTest_jniCreateNewObject(JNIEnv* env, + jclass) { return jni::GlobalObject{1, 2, 3}.Release(); } @@ -46,8 +47,8 @@ JNIEXPORT jobject JNICALL Java_com_jnibind_android_GlobalObjectTest_jniBuildNewObjectsFromExistingObjects( JNIEnv* env, jclass, jobject test_helper_object, jobject object_to_mutate) { jni::LocalObject helper_obj{object_to_mutate}; - return jni::LocalObject{test_helper_object}( - "methodTakesGlobalObjectReturnsNewObject", helper_obj) + return jni::LocalObject{test_helper_object} + .Call<"methodTakesGlobalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -55,17 +56,17 @@ JNIEXPORT jobject JNICALL Java_com_jnibind_android_GlobalObjectTest_jniManipulateNewGlobalObjectSetIntVal238( JNIEnv* env, jclass, jobject jtest_obj) { jni::GlobalObject helper_obj{2, 3, 8}; - return jni::LocalObject{jtest_obj}( - "methodTakesGlobalObjectReturnsNewObject", std::move(helper_obj)) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesGlobalObjectReturnsNewObject">(std::move(helper_obj)) .Release(); } JNIEXPORT jobject JNICALL Java_com_jnibind_android_GlobalObjectTest_jniMaterializeNewGlobalObjectSetIntVal159( JNIEnv* env, jclass, jobject jtest_obj) { - return jni::LocalObject{jtest_obj}( - "methodTakesGlobalObjectReturnsNewObject", - jni::GlobalObject{1, 5, 9}) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesGlobalObjectReturnsNewObject">( + jni::GlobalObject{1, 5, 9}) .Release(); } diff --git a/javatests/com/jnibind/android/local_object_test_jni.cc b/javatests/com/jnibind/android/local_object_test_jni.cc index 0ce67f13..9897466f 100644 --- a/javatests/com/jnibind/android/local_object_test_jni.cc +++ b/javatests/com/jnibind/android/local_object_test_jni.cc @@ -32,7 +32,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { } JNIEXPORT jobject JNICALL -Java_com_jnibind_android_LocalObjectTest_jniCreateNewObject(JNIEnv* env, jclass) { +Java_com_jnibind_android_LocalObjectTest_jniCreateNewObject(JNIEnv* env, + jclass) { return jni::LocalObject{1, 2, 3}.Release(); } @@ -53,8 +54,8 @@ JNIEXPORT jobject JNICALL Java_com_jnibind_android_LocalObjectTest_jniBuildNewObjectsFromExistingObjects( JNIEnv* env, jclass, jobject jtest_obj, jobject jhelper_obj) { jni::LocalObject helper_obj{jhelper_obj}; - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", helper_obj) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -62,17 +63,17 @@ JNIEXPORT jobject JNICALL Java_com_jnibind_android_LocalObjectTest_jniManipulateNewLocalObjectSetIntVal238( JNIEnv* env, jclass, jobject jtest_obj) { jni::LocalObject helper_obj{2, 3, 8}; - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", std::move(helper_obj)) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(std::move(helper_obj)) .Release(); } JNIEXPORT jobject JNICALL Java_com_jnibind_android_LocalObjectTest_jniMaterializeNewLocalObjectSetIntVal159( JNIEnv* env, jclass, jobject jtest_obj) { - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", - jni::LocalObject{1, 5, 9}) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">( + jni::LocalObject{1, 5, 9}) .Release(); } diff --git a/javatests/com/jnibind/android/method_test_jni.cc b/javatests/com/jnibind/android/method_test_jni.cc index b558a0a1..d4337188 100644 --- a/javatests/com/jnibind/android/method_test_jni.cc +++ b/javatests/com/jnibind/android/method_test_jni.cc @@ -78,42 +78,39 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { JNIEXPORT void JNICALL Java_com_jnibind_android_MethodTest_jniVoidMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - rjni_test_helper("voidMethod"); + rjni_test_helper.Call<"voidMethod">(); } JNIEXPORT void JNICALL -Java_com_jnibind_android_MethodTest_jniVoidMethodTakesOneInt(JNIEnv* env, jclass, - jobject object, - jint i) { +Java_com_jnibind_android_MethodTest_jniVoidMethodTakesOneInt(JNIEnv* env, + jclass, + jobject object, + jint i) { LocalObject rjni_test_helper{object}; - rjni_test_helper("voidMethodTakesOneInt", jint{i}); + rjni_test_helper.Call<"voidMethodTakesOneInt">(jint{i}); } JNIEXPORT void JNICALL -Java_com_jnibind_android_MethodTest_jniVoidMethodTakesFiveInts(JNIEnv* env, jclass, - jobject object, - jint i1, jint i2, - jint i3, jint i4, - jint i5) { +Java_com_jnibind_android_MethodTest_jniVoidMethodTakesFiveInts( + JNIEnv* env, jclass, jobject object, jint i1, jint i2, jint i3, jint i4, + jint i5) { LocalObject rjni_test_helper{object}; - rjni_test_helper("voidMethodTakesFiveInts", jint{i1}, jint{i2}, jint{i3}, - jint{i4}, jint{i5}); + rjni_test_helper.Call<"voidMethodTakesFiveInts">(jint{i1}, jint{i2}, jint{i3}, + jint{i4}, jint{i5}); } /** Boolean Method Tests. */ JNIEXPORT jboolean JNICALL Java_com_jnibind_android_MethodTest_jniBooleanMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("booleanMethod"); + return rjni_test_helper.Call<"booleanMethod">(); } JNIEXPORT jboolean JNICALL -Java_com_jnibind_android_MethodTest_jniBooleanMethodTakesOneBoolean(JNIEnv* env, - jclass, - jobject object, - jboolean i) { +Java_com_jnibind_android_MethodTest_jniBooleanMethodTakesOneBoolean( + JNIEnv* env, jclass, jobject object, jboolean i) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("booleanMethodTakesOneBoolean", jboolean{i}); + return rjni_test_helper.Call<"booleanMethodTakesOneBoolean">(jboolean{i}); } JNIEXPORT jboolean JNICALL @@ -121,48 +118,48 @@ Java_com_jnibind_android_MethodTest_jniBooleanMethodTakesFiveBooleans( JNIEnv* env, jclass, jobject object, jboolean i1, jboolean i2, jboolean i3, jboolean i4, jboolean i5) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("booleanMethodTakesFiveBooleans", jboolean{i1}, - jboolean{i2}, jboolean{i3}, jboolean{i4}, - jboolean{i5}); + return rjni_test_helper.Call<"booleanMethodTakesFiveBooleans">( + jboolean{i1}, jboolean{i2}, jboolean{i3}, jboolean{i4}, jboolean{i5}); } /** Int Method Tests. */ JNIEXPORT jint JNICALL Java_com_jnibind_android_MethodTest_jniIntMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("intMethod"); + return rjni_test_helper.Call<"intMethod">(); } -JNIEXPORT jint JNICALL Java_com_jnibind_android_MethodTest_jniIntMethodTakesOneInt( - JNIEnv* env, jclass, jobject object, jint i) { +JNIEXPORT jint JNICALL +Java_com_jnibind_android_MethodTest_jniIntMethodTakesOneInt(JNIEnv* env, jclass, + jobject object, + jint i) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("intMethodTakesOneInt", jint{i}); + return rjni_test_helper.Call<"intMethodTakesOneInt">(jint{i}); } JNIEXPORT jint JNICALL -Java_com_jnibind_android_MethodTest_jniIntMethodTakesFiveInts(JNIEnv* env, jclass, - jobject object, - jint i1, jint i2, - jint i3, jint i4, - jint i5) { +Java_com_jnibind_android_MethodTest_jniIntMethodTakesFiveInts( + JNIEnv* env, jclass, jobject object, jint i1, jint i2, jint i3, jint i4, + jint i5) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("intMethodTakesFiveInts", jint{i1}, jint{i2}, - jint{i3}, jint{i4}, jint{i5}); + return rjni_test_helper.Call<"intMethodTakesFiveInts">( + jint{i1}, jint{i2}, jint{i3}, jint{i4}, jint{i5}); } /** Long Method Tests. */ JNIEXPORT jlong JNICALL Java_com_jnibind_android_MethodTest_jniLongMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("longMethod"); + return rjni_test_helper.Call<"longMethod">(); } JNIEXPORT jlong JNICALL -Java_com_jnibind_android_MethodTest_jniLongMethodTakesOneLong(JNIEnv* env, jclass, - jobject object, - jlong i) { +Java_com_jnibind_android_MethodTest_jniLongMethodTakesOneLong(JNIEnv* env, + jclass, + jobject object, + jlong i) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("longMethodTakesOneLong", jlong{i}); + return rjni_test_helper.Call<"longMethodTakesOneLong">(jlong{i}); } JNIEXPORT jlong JNICALL @@ -170,24 +167,24 @@ Java_com_jnibind_android_MethodTest_jniLongMethodTakesFiveLongs( JNIEnv* env, jclass, jobject object, jlong i1, jlong i2, jlong i3, jlong i4, jlong i5) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("longMethodTakesFiveLongs", jlong{i1}, jlong{i2}, - jlong{i3}, jlong{i4}, jlong{i5}); + return rjni_test_helper.Call<"longMethodTakesFiveLongs">( + jlong{i1}, jlong{i2}, jlong{i3}, jlong{i4}, jlong{i5}); } /** Float Method Tests. */ JNIEXPORT jfloat JNICALL Java_com_jnibind_android_MethodTest_jniFloatMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("floatMethod"); + return rjni_test_helper.Call<"floatMethod">(); } JNIEXPORT jfloat JNICALL Java_com_jnibind_android_MethodTest_jniFloatMethodTakesOneFloat(JNIEnv* env, - jclass, - jobject object, - jfloat i) { + jclass, + jobject object, + jfloat i) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("floatMethodTakesOneFloat", jfloat{i}); + return rjni_test_helper.Call<"floatMethodTakesOneFloat">(jfloat{i}); } JNIEXPORT jfloat JNICALL @@ -195,24 +192,22 @@ Java_com_jnibind_android_MethodTest_jniFloatMethodTakesFiveFloats( JNIEnv* env, jclass, jobject object, jfloat i1, jfloat i2, jfloat i3, jfloat i4, jfloat i5) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("floatMethodTakesFiveFloats", jfloat{i1}, jfloat{i2}, - jfloat{i3}, jfloat{i4}, jfloat{i5}); + return rjni_test_helper.Call<"floatMethodTakesFiveFloats">( + jfloat{i1}, jfloat{i2}, jfloat{i3}, jfloat{i4}, jfloat{i5}); } /** Double Method Tests. */ JNIEXPORT jdouble JNICALL Java_com_jnibind_android_MethodTest_jniDoubleMethod( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("doubleMethod"); + return rjni_test_helper.Call<"doubleMethod">(); } JNIEXPORT jdouble JNICALL -Java_com_jnibind_android_MethodTest_jniDoubleMethodTakesOneDouble(JNIEnv* env, - jclass, - jobject object, - jdouble i) { +Java_com_jnibind_android_MethodTest_jniDoubleMethodTakesOneDouble( + JNIEnv* env, jclass, jobject object, jdouble i) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("doubleMethodTakesOneDouble", jdouble{i}); + return rjni_test_helper.Call<"doubleMethodTakesOneDouble">(jdouble{i}); } JNIEXPORT jdouble JNICALL @@ -220,27 +215,27 @@ Java_com_jnibind_android_MethodTest_jniDoubleMethodTakesFiveDoubles( JNIEnv* env, jclass, jobject object, jdouble i1, jdouble i2, jdouble i3, jdouble i4, jdouble i5) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("doubleMethodTakesFiveDoubles", jdouble{i1}, - jdouble{i2}, jdouble{i3}, jdouble{i4}, jdouble{i5}); + return rjni_test_helper.Call<"doubleMethodTakesFiveDoubles">( + jdouble{i1}, jdouble{i2}, jdouble{i3}, jdouble{i4}, jdouble{i5}); } /** Overload Method Tests. */ JNIEXPORT int JNICALL Java_com_jnibind_android_MethodTest_jniCallFooOverload1( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("foo"); + return rjni_test_helper.Call<"foo">(); } JNIEXPORT int JNICALL Java_com_jnibind_android_MethodTest_jniCallFooOverload2( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("foo", 123.f); + return rjni_test_helper.Call<"foo">(123.f); } JNIEXPORT int JNICALL Java_com_jnibind_android_MethodTest_jniCallFooOverload3( JNIEnv* env, jclass, jobject object) { LocalObject rjni_test_helper{object}; - return rjni_test_helper("foo", 123.f, 456.f); + return rjni_test_helper.Call<"foo">(123.f, 456.f); } } // extern "C" diff --git a/javatests/com/jnibind/android/string_test_jni.cc b/javatests/com/jnibind/android/string_test_jni.cc index b75ae007..e440eed2 100644 --- a/javatests/com/jnibind/android/string_test_jni.cc +++ b/javatests/com/jnibind/android/string_test_jni.cc @@ -51,9 +51,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { /** Void return type tests. */ JNIEXPORT void JNICALL -Java_com_jnibind_android_StringTest_jniVoidMethodTakesString(JNIEnv* env, jclass, - jobject object, - jstring string) { +Java_com_jnibind_android_StringTest_jniVoidMethodTakesString(JNIEnv* env, + jclass, + jobject object, + jstring string) { LocalObject r_jni_string_test_helper{object}; // TODO(b/175083373): The following (and below) should compile with overload // sets. @@ -62,17 +63,17 @@ Java_com_jnibind_android_StringTest_jniVoidMethodTakesString(JNIEnv* env, jclass r_jni_string_test_helper.GetMethod("voidMethodTakesString", std::string{LocalString{string}.Pin().ToString()}); */ - r_jni_string_test_helper("voidMethodTakesString", - std::string{LocalString{string}.Pin().ToString()}); + r_jni_string_test_helper.Call<"voidMethodTakesString">( + std::string{LocalString{string}.Pin().ToString()}); } JNIEXPORT void JNICALL Java_com_jnibind_android_StringTest_jniVoidMethodTakesTwoStrings( JNIEnv* env, jclass, jobject object, jstring s1, jstring s2) { LocalObject r_jni_string_test_helper{object}; - r_jni_string_test_helper("voidMethodTakesTwoStrings", - std::string{LocalString{s1}.Pin().ToString()}, - std::string{LocalString{s2}.Pin().ToString()}); + r_jni_string_test_helper.Call<"voidMethodTakesTwoStrings">( + std::string{LocalString{s1}.Pin().ToString()}, + std::string{LocalString{s2}.Pin().ToString()}); } JNIEXPORT void JNICALL @@ -80,26 +81,26 @@ Java_com_jnibind_android_StringTest_jniVoidMethodTakesFiveStrings( JNIEnv* env, jclass, jobject object, jstring s1, jstring s2, jstring s3, jstring s4, jstring s5) { LocalObject r_jni_string_test_helper{object}; - r_jni_string_test_helper("voidMethodTakesFiveStrings", - std::string{LocalString{s1}.Pin().ToString()}, - std::string{LocalString{s2}.Pin().ToString()}, - std::string{LocalString{s3}.Pin().ToString()}, - std::string{LocalString{s4}.Pin().ToString()}, - std::string{LocalString{s5}.Pin().ToString()}); + r_jni_string_test_helper.Call<"voidMethodTakesFiveStrings">( + std::string{LocalString{s1}.Pin().ToString()}, + std::string{LocalString{s2}.Pin().ToString()}, + std::string{LocalString{s3}.Pin().ToString()}, + std::string{LocalString{s4}.Pin().ToString()}, + std::string{LocalString{s5}.Pin().ToString()}); } /** String return type tests. */ JNIEXPORT jstring JNICALL -Java_com_jnibind_android_StringTest_jniStringMethodTakesString(JNIEnv* env, jclass, - jobject object, - jstring string) { +Java_com_jnibind_android_StringTest_jniStringMethodTakesString(JNIEnv* env, + jclass, + jobject object, + jstring string) { LocalObject r_jni_string_test_helper{object}; - // TODO(b/174272629): This declaration is clumsy because Return is - // an ObjectRef and thus not implicitly convertible to jstring. The following - // needs to be simpler to express. - return LocalString{r_jni_string_test_helper( - "stringMethodTakesString", + // TODO(b/174272629): This declaration is clumsy because Return + // is an ObjectRef and thus not implicitly convertible to jstring. The + // following needs to be simpler to express. + return LocalString{r_jni_string_test_helper.Call<"stringMethodTakesString">( std::string{LocalString{string}.Pin().ToString()})} .Release(); } @@ -109,9 +110,9 @@ Java_com_jnibind_android_StringTest_jniStringMethodTakesTwoStrings( JNIEnv* env, jclass, jobject object, jstring s1, jstring s2) { LocalObject r_jni_string_test_helper{object}; return LocalString{ - r_jni_string_test_helper("stringMethodTakesTwoStrings", - std::string{LocalString{s1}.Pin().ToString()}, - std::string{LocalString{s2}.Pin().ToString()})} + r_jni_string_test_helper.Call<"stringMethodTakesTwoStrings">( + std::string{LocalString{s1}.Pin().ToString()}, + std::string{LocalString{s2}.Pin().ToString()})} .Release(); } @@ -121,12 +122,12 @@ Java_com_jnibind_android_StringTest_jniStringMethodTakesFiveStrings( jstring s4, jstring s5) { LocalObject r_jni_string_test_helper{object}; return LocalString{ - r_jni_string_test_helper("stringMethodTakesFiveStrings", - std::string{LocalString{s1}.Pin().ToString()}, - std::string{LocalString{s2}.Pin().ToString()}, - std::string{LocalString{s3}.Pin().ToString()}, - std::string{LocalString{s4}.Pin().ToString()}, - std::string{LocalString{s5}.Pin().ToString()})} + r_jni_string_test_helper.Call<"stringMethodTakesFiveStrings">( + std::string{LocalString{s1}.Pin().ToString()}, + std::string{LocalString{s2}.Pin().ToString()}, + std::string{LocalString{s3}.Pin().ToString()}, + std::string{LocalString{s4}.Pin().ToString()}, + std::string{LocalString{s5}.Pin().ToString()})} .Release(); } diff --git a/javatests/com/jnibind/android/thread_test_jni.cc b/javatests/com/jnibind/android/thread_test_jni.cc index 992db5ba..a5708fc8 100644 --- a/javatests/com/jnibind/android/thread_test_jni.cc +++ b/javatests/com/jnibind/android/thread_test_jni.cc @@ -62,7 +62,7 @@ Java_com_jnibind_android_ThreadTest_runsThreadedWorkOnObject(JNIEnv* env, ThreadGuard thread_guard{}; // This will crash without fallback loader (`jclass` will be unknown). - fixture["objectTestHelper"].Get()("foo"); + fixture["objectTestHelper"].Get().Call<"foo">(); }}; worker.join(); diff --git a/javatests/com/jnibind/test/BUILD b/javatests/com/jnibind/test/BUILD index 8af8d7f6..1f95abc6 100644 --- a/javatests/com/jnibind/test/BUILD +++ b/javatests/com/jnibind/test/BUILD @@ -53,6 +53,7 @@ cc_library( ":object_test_helper_jni", "//:jni_bind", "//metaprogramming:lambda_string", + "//metaprogramming:string_literal", ], alwayslink = True, ) diff --git a/javatests/com/jnibind/test/array_test_field_rank_1_jni.cc b/javatests/com/jnibind/test/array_test_field_rank_1_jni.cc index 21abf4c9..82def753 100644 --- a/javatests/com/jnibind/test/array_test_field_rank_1_jni.cc +++ b/javatests/com/jnibind/test/array_test_field_rank_1_jni.cc @@ -33,6 +33,7 @@ using ::jni::LocalArray; using ::jni::LocalObject; using ::jni::LocalString; using ::jni::StaticRef; +using ::jni::metaprogramming::StringLiteral; static std::unique_ptr> jvm; @@ -58,17 +59,14 @@ static constexpr Class kArrayTestFieldRank1 { // Generic field test suitable for simple primitive types. // Strings are passed through lambdas as field indexing is compile time. -template +template void GenericFieldTest(LocalObject fixture, - SpanType max_val, SpanType base, SpanType stride, - FieldNameLambda field_name_lambda, - MethodNameLambda method_name_lambda) { + SpanType max_val, SpanType base, SpanType stride) { // Field starts in default state. - LocalArray arr{fixture[field_name_lambda()].Get()}; - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{0}, - fixture[field_name_lambda()].Get()); + LocalArray arr{fixture.Access().Get()}; + StaticRef{}.Call( + SpanType{0}, SpanType{0}, fixture.Access().Get()); // Updating the field manually works. { @@ -78,9 +76,8 @@ void GenericFieldTest(LocalObject fixture, } } - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{1}, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + SpanType{0}, SpanType{1}, fixture.Access().Get()); // Updating the field repeatedly works. { @@ -89,13 +86,15 @@ void GenericFieldTest(LocalObject fixture, pin.ptr()[i] = base + static_cast(stride * i); } } - StaticRef{}(method_name_lambda(), base, stride, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + base, stride, fixture.Access().Get()); // Not pinning the value has no effect. - { std::memset(arr.Pin(false).ptr(), 0, arr.Length() * sizeof(SpanType)); } - StaticRef{}(method_name_lambda(), base, stride, - fixture[field_name_lambda()].Get()); + { + std::memset(arr.Pin(false).ptr(), 0, arr.Length() * sizeof(SpanType)); + } + StaticRef{}.Call( + base, stride, fixture.Access().Get()); } extern "C" { @@ -113,64 +112,64 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_jniTearDown( JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeBooleanTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - jboolean{true}, jboolean{0}, jboolean{true}, - STR("booleanArrayField"), STR("assertBoolean1D")); + GenericFieldTest( + LocalObject{test_fixture}, jboolean{true}, + jboolean{0}, jboolean{true}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeByteTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jbyte{10}, jbyte{1}, - STR("byteArrayField"), STR("assertByte1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jbyte{10}, jbyte{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeCharTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jchar{10}, jchar{1}, - STR("charArrayField"), STR("assertChar1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jchar{10}, jchar{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeShortTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jshort{10}, jshort{1}, - STR("shortArrayField"), STR("assertShort1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jshort{10}, jshort{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeIntTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jint{10}, jint{1}, - STR("intArrayField"), STR("assertInt1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jint{10}, jint{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeLongTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jlong{10}, jlong{1}, - STR("longArrayField"), STR("assertLong1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jlong{10}, jlong{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeFloatTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jfloat{10.f}, - jfloat{1.f}, STR("floatArrayField"), STR("assertFloat1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jfloat{10.f}, jfloat{1.f}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank1_nativeDoubleTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jdouble{10}, jdouble{1}, - STR("doubleArrayField"), STR("assertDouble1D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jdouble{10}, jdouble{1}); } JNIEXPORT void JNICALL @@ -179,41 +178,44 @@ Java_com_jnibind_test_ArrayTestFieldRank1_nativeStringTests( LocalObject fixture{test_fixture}; // Field starts in default state. - StaticRef{}( - "assertString1D", fixture["stringArrayField"].Get(), jboolean{false}); + StaticRef{}("assertString1D", + fixture.Access<"stringArrayField">().Get(), + jboolean{false}); // Updating the field manually works. - LocalArray arr = fixture["stringArrayField"].Get(); + LocalArray arr = fixture.Access<"stringArrayField">().Get(); arr.Set(0, LocalString{"Foo"}); arr.Set(1, LocalString{"Baz"}); arr.Set(2, LocalString{"Bar"}); - StaticRef{}("assertString1D", arr, true); + StaticRef{}.Call<"assertString1D">(arr, true); // Updating the field repeatedly works. arr.Set(0, LocalString{"FAKE"}); arr.Set(1, LocalString{"DEAD"}); arr.Set(2, LocalString{"BEEF"}); - StaticRef{}("assertString1D", arr, false); + StaticRef{}.Call<"assertString1D">(arr, false); // Updating the field repeatedly works. arr.Set(0, "Foo"); const char* kBaz = "Baz"; arr.Set(1, kBaz); arr.Set(2, std::string{"Bar"}); - StaticRef{}("assertString1D", arr, true); + StaticRef{}.Call<"assertString1D">(arr, true); // Iterating over values works. static constexpr std::array vals{"Foo", "Baz", "Bar"}; int i = 0; for (LocalString local_string : arr.Pin()) { - StaticRef{}("assertString", local_string, vals[i]); + StaticRef{}.Call<"assertString">(local_string, + vals[i]); ++i; } // Iterating over passed values works. i = 0; for (LocalString local_string : LocalArray{object_array}.Pin()) { - StaticRef{}("assertString", local_string, vals[i]); + StaticRef{}.Call<"assertString">(local_string, + vals[i]); ++i; } } @@ -223,12 +225,12 @@ Java_com_jnibind_test_ArrayTestFieldRank1_nativeObjectTests( JNIEnv* env, jclass, jobject test_fixture, jobjectArray) { LocalObject fixture{test_fixture}; LocalArray arr = - fixture["objectArrayField"].Get(); + fixture.Access<"objectArrayField">().Get(); int i = 0; for (LocalObject new_obj : fixture["objectArrayField"].Get().Pin()) { - StaticRef{}("assertObject", i, new_obj); + StaticRef{}.Call<"assertObject">(i, new_obj); i++; } } diff --git a/javatests/com/jnibind/test/array_test_field_rank_2_jni.cc b/javatests/com/jnibind/test/array_test_field_rank_2_jni.cc index a645e6ef..d27d2f5f 100644 --- a/javatests/com/jnibind/test/array_test_field_rank_2_jni.cc +++ b/javatests/com/jnibind/test/array_test_field_rank_2_jni.cc @@ -23,6 +23,7 @@ #include "object_test_helper_jni.h" #include "jni_bind.h" #include "metaprogramming/lambda_string.h" +#include "metaprogramming/string_literal.h" using ::jni::Array; using ::jni::ArrayView; @@ -33,6 +34,7 @@ using ::jni::LocalObject; using ::jni::Modulo; using ::jni::Rank; using ::jni::StaticRef; +using ::jni::metaprogramming::StringLiteral; static std::unique_ptr> jvm; @@ -58,18 +60,15 @@ static constexpr Class kArrayTestFieldRank2 { // Generic field test suitable for simple primitive types. // Strings are passed through lambdas as field indexing is compile time. -template +template void GenericFieldTest(LocalObject fixture, - SpanType max_val, SpanType base, SpanType stride, - FieldNameLambda field_name_lambda, - MethodNameLambda method_name_lambda) { - LocalArray arr{fixture[field_name_lambda()].Get()}; + SpanType max_val, SpanType base, SpanType stride) { + LocalArray arr{fixture.Access().Get()}; // Field starts in default state. - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{0}, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + SpanType{0}, SpanType{0}, fixture.Access().Get()); // Updating the field manually works. { @@ -82,8 +81,8 @@ void GenericFieldTest(LocalObject fixture, } } - StaticRef{}(method_name_lambda(), base, SpanType{1}, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + base, SpanType{1}, fixture.Access().Get()); } // Updating the field repeatedly works. @@ -101,8 +100,8 @@ void GenericFieldTest(LocalObject fixture, } } } - StaticRef{}(method_name_lambda(), base, stride, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + base, stride, fixture.Access().Get()); } } @@ -113,8 +112,8 @@ void GenericFieldTest(LocalObject fixture, arr_rank_1.Length() * sizeof(SpanType)); } } - StaticRef{}(method_name_lambda(), base, stride, - fixture[field_name_lambda()].Get()); + StaticRef{}.Call( + base, stride, fixture.Access().Get()); } extern "C" { @@ -132,68 +131,68 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_jniTearDown( JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeBooleanTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, jboolean{2}, - jboolean{0}, jboolean{1}, STR("booleanArrayField"), - STR("assertBoolean2D")); + GenericFieldTest( + LocalObject{test_fixture}, jboolean{2}, jboolean{0}, + jboolean{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeByteTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jbyte{10}, jbyte{2}, - STR("byteArrayField"), STR("assertByte2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jbyte{10}, jbyte{2}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeCharTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jchar{0}, jchar{1}, - STR("charArrayField"), STR("assertChar2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jchar{0}, jchar{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeShortTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jshort{0}, jshort{1}, - STR("shortArrayField"), STR("assertShort2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jshort{0}, jshort{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeIntTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jint{0}, jint{1}, - STR("intArrayField"), STR("assertInt2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jint{0}, jint{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeLongTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jlong{0}, jlong{1}, - STR("longArrayField"), STR("assertLong2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jlong{0}, jlong{1}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeFloatTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jfloat{0.f}, jfloat{1.f}, - STR("floatArrayField"), STR("assertFloat2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jfloat{0.f}, jfloat{1.f}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeDoubleTests( JNIEnv* env, jclass, jobject test_fixture) { - GenericFieldTest(LocalObject{test_fixture}, - std::numeric_limits::max(), jdouble{0}, jdouble{1}, - STR("doubleArrayField"), STR("assertDouble2D")); + GenericFieldTest( + LocalObject{test_fixture}, + std::numeric_limits::max(), jdouble{0}, jdouble{1}); } // TODO(b/143908983): This is broken, but using regular `jobject` works, -// so there is still an alternative path to be take. +// so there is still an alternative path to be taken. JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestFieldRank2_nativeStringTests( JNIEnv* env, jclass, jobject test_fixture) { @@ -202,10 +201,11 @@ Java_com_jnibind_test_ArrayTestFieldRank2_nativeStringTests( // Field starts in default state. StaticRef{}( - "assertString1D", fixture["stringArrayField"].Get(), jboolean{false}); + "assertString1D", fixture.Access<"stringArrayField">().Get(), + jboolean{false}); // Updating the field manually works. - LocalArray arr = fixture["stringArrayField"].Get(); + LocalArray arr = fixture.Access<"stringArrayField">().Get(); arr.Set(0, LocalString{"Foo"}); arr.Set(1, LocalString{"Baz"}); arr.Set(2, LocalString{"Bar"}); @@ -245,29 +245,29 @@ void Java_com_jnibind_test_ArrayTestFieldRank2_nativeObjectTests( JNIEnv* env, jclass, jobject test_fixture) { LocalObject fixture{test_fixture}; LocalArray arr = - fixture["objectArrayField"].Get(); + fixture.Access<"objectArrayField">().Get(); int i = 0; { for (LocalArray arr_rank_1 : arr.Pin()) { for (LocalObject obj : arr_rank_1.Pin()) { - obj["intVal1"].Set(i); + obj.Access<"intVal1">().Set(i); i++; - obj["intVal2"].Set(i); + obj.Access<"intVal2">().Set(i); i++; - obj["intVal3"].Set(i); + obj.Access<"intVal3">().Set(i); i++; } } } // Object on fixture is already updated. - StaticRef{}("assertObject2D", 0, 1, - fixture["objectArrayField"].Get()); + StaticRef{}.Call<"assertObject2D">( + 0, 1, fixture.Access<"objectArrayField">().Get()); // But using local reference works too. - StaticRef{}("assertObject2D", 0, 1, arr); + StaticRef{}.Call<"assertObject2D">(0, 1, arr); } } // extern "C" diff --git a/javatests/com/jnibind/test/array_test_method_rank_1_jni.cc b/javatests/com/jnibind/test/array_test_method_rank_1_jni.cc index 42d85212..c664db59 100644 --- a/javatests/com/jnibind/test/array_test_method_rank_1_jni.cc +++ b/javatests/com/jnibind/test/array_test_method_rank_1_jni.cc @@ -21,7 +21,6 @@ #include "array_test_helpers_native.h" #include "object_test_helper_jni.h" #include "jni_bind.h" -#include "metaprogramming/lambda_string.h" using ::jni::ArrayView; using ::jni::LocalArray; @@ -29,21 +28,21 @@ using ::jni::LocalObject; using ::jni::LocalString; using ::jni::RegularToArrayTypeMap_t; using ::jni::StaticRef; +using ::jni::metaprogramming::StringLiteral; static std::unique_ptr> jvm; // Generic method test suitable for simple primitive types. // Strings are passed through lambdas as method indexing is compile time. -template -void GenericMethodTest(LocalArray local_arr, SpanType base, - MethodNameLambda method_name_lambda) { +template +void GenericMethodTest(LocalArray local_arr, SpanType base) { // Simple lvalue pass through works as expected. - StaticRef{}(method_name_lambda(), base, SpanType{1}, - local_arr); + StaticRef{}.Call( + base, SpanType{1}, local_arr); // Simple rvalue pass through works as expected. - StaticRef{}(method_name_lambda(), base, SpanType{1}, - std::move(local_arr)); + StaticRef{}.Call( + base, SpanType{1}, std::move(local_arr)); // Building a new array, and setting all the values by hand works. LocalArray new_array{8}; @@ -53,8 +52,8 @@ void GenericMethodTest(LocalArray local_arr, SpanType base, array_view.ptr()[i] = base + static_cast(i); } } - StaticRef{}(method_name_lambda(), base, SpanType{1}, - new_array); + StaticRef{}.Call( + base, SpanType{1}, new_array); // You can pull the view multiple times. { @@ -72,8 +71,8 @@ void GenericMethodTest(LocalArray local_arr, SpanType base, val = base + i; i++; } - StaticRef{}(method_name_lambda(), base, SpanType{1}, - new_array); + StaticRef{}.Call( + base, SpanType{1}, new_array); // You can build an array of null values and set the values manually. LocalArray arr_built_from_null{5}; @@ -84,8 +83,8 @@ void GenericMethodTest(LocalArray local_arr, SpanType base, val = base + j; j++; } - StaticRef{}(method_name_lambda(), base, SpanType{1}, - new_array); + StaticRef{}.Call( + base, SpanType{1}, new_array); } extern "C" { @@ -103,57 +102,57 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_jniTearDown( JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeBooleanTests( JNIEnv* env, jclass, jbooleanArray boolean_array) { - GenericMethodTest(LocalArray{boolean_array}, jboolean{true}, - STR("assertBoolean1D")); + GenericMethodTest( + LocalArray{boolean_array}, jboolean{true}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeByteTests( JNIEnv* env, jclass, jbyteArray byte_array) { - GenericMethodTest(LocalArray{byte_array}, jbyte{0}, - STR("assertByte1D")); + GenericMethodTest(LocalArray{byte_array}, + jbyte{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeCharTests( JNIEnv* env, jclass, jcharArray char_array) { - GenericMethodTest(LocalArray{char_array}, jchar{0}, - STR("assertChar1D")); + GenericMethodTest(LocalArray{char_array}, + jchar{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeShortTests( JNIEnv* env, jclass, jshortArray short_array) { - GenericMethodTest(LocalArray{short_array}, jshort{0}, - STR("assertShort1D")); + GenericMethodTest(LocalArray{short_array}, + jshort{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeIntTests(JNIEnv* env, jclass, jintArray int_array) { - GenericMethodTest(LocalArray{int_array}, jint{0}, STR("assertInt1D")); + GenericMethodTest(LocalArray{int_array}, jint{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeLongTests( JNIEnv* env, jclass, jlongArray long_array) { - GenericMethodTest(LocalArray{long_array}, jlong{0}, - STR("assertLong1D")); + GenericMethodTest(LocalArray{long_array}, + jlong{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeFloatTests( JNIEnv* env, jclass, jfloatArray float_array) { - GenericMethodTest(LocalArray{float_array}, jfloat{0}, - STR("assertFloat1D")); + GenericMethodTest(LocalArray{float_array}, + jfloat{0}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank1_nativeDoubleTests( JNIEnv* env, jclass, jdoubleArray double_array) { - GenericMethodTest(LocalArray{double_array}, jdouble{0}, - STR("assertDouble1D")); + GenericMethodTest( + LocalArray{double_array}, jdouble{0}); } JNIEXPORT void JNICALL @@ -161,7 +160,7 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeStringTests( JNIEnv* env, jclass, jobjectArray object_array) { // Simple lvalue pass through works as expected. LocalArray local_arr{object_array}; - StaticRef{}("assertString1D", local_arr, true); + StaticRef{}.Call<"assertString1D">(local_arr, true); // TODO(b/143908983): Currently not possible to write. // Simple rvalue pass through works as expected. @@ -177,7 +176,7 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeStringTests( new_array.Set(0, LocalString{"Foo"}); new_array.Set(1, LocalString{"Baz"}); new_array.Set(2, LocalString{"Bar"}); - StaticRef{}("assertString1D", new_array, true); + StaticRef{}.Call<"assertString1D">(new_array, true); // And it can be iterated over. std::size_t i = 0; @@ -187,7 +186,8 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeStringTests( i++; } - StaticRef{}("assertString1D", validator_array, true); + StaticRef{}.Call<"assertString1D">(validator_array, + true); } JNIEXPORT void JNICALL @@ -195,18 +195,17 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeObjectTests( JNIEnv* env, jclass, jobjectArray object_array) { // Creating arrays of nulls with just size works. LocalArray local_arr_nulls{5}; - StaticRef{}("assertObjectArrayOfNulls1D", - local_arr_nulls); + StaticRef{}.Call<"assertObjectArrayOfNulls1D">( + local_arr_nulls); // Simple lvalue pass through works as expected. LocalArray local_arr{object_array}; - StaticRef{}("assertObject1D", 0, local_arr); + StaticRef{}.Call<"assertObject1D">(0, local_arr); // Simple rvalue pass through works as expected. - StaticRef{}( - "assertObject1D", 5, - LocalArray{ - 1, LocalObject{5, 5, 5}}); + StaticRef{}.Call<"assertObject1D">( + 5, LocalArray{ + 1, LocalObject{5, 5, 5}}); // Building a new array, and setting all the values by hand works. LocalObject obj{0, 0, 0}; @@ -217,14 +216,14 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeObjectTests( i, LocalObject{jint{i}, jint{i}, jint{i}}); } } - StaticRef{}("assertObject1D", 0, new_array); + StaticRef{}.Call<"assertObject1D">(0, new_array); // You can pull the view multiple times. { for (int i = 0; i < new_array.Length(); ++i) { - new_array.Set(i, - LocalObject{2, 2, 2}( - "returnNewObjectWithFieldSetToSum", new_array.Get(i))); + new_array.Set( + i, LocalObject{2, 2, 2} + .Call<"returnNewObjectWithFieldSetToSum">(new_array.Get(i))); } } @@ -233,12 +232,12 @@ Java_com_jnibind_test_ArrayTestMethodRank1_nativeObjectTests( // is being iterated over, and upcast to a LocalObject. // Also note: Pin is not to contiguous memory, so no copyback bool either. for (LocalObject val : new_array.Pin()) { - val["intVal1"].Set(val["intVal1"].Get() + 3); - val["intVal2"].Set(val["intVal2"].Get() + 3); - val["intVal3"].Set(val["intVal3"].Get() + 3); + val.Access<"intVal1">().Set(val["intVal1"].Get() + 3); + val.Access<"intVal2">().Set(val["intVal2"].Get() + 3); + val.Access<"intVal3">().Set(val["intVal3"].Get() + 3); } - StaticRef{}("assertObject1D", 2 + 3, new_array); + StaticRef{}.Call<"assertObject1D">(2 + 3, new_array); } } // extern "C" diff --git a/javatests/com/jnibind/test/array_test_method_rank_2_jni.cc b/javatests/com/jnibind/test/array_test_method_rank_2_jni.cc index 6de4baa4..40d535f8 100644 --- a/javatests/com/jnibind/test/array_test_method_rank_2_jni.cc +++ b/javatests/com/jnibind/test/array_test_method_rank_2_jni.cc @@ -22,29 +22,29 @@ #include "modulo.h" #include "javatests/com/jnibind/test/object_test_helper_jni.h" #include "jni_bind.h" -#include "metaprogramming/lambda_string.h" using ::jni::LocalArray; using ::jni::LocalObject; using ::jni::Modulo; using ::jni::RegularToArrayTypeMap_t; using ::jni::StaticRef; +using ::jni::metaprogramming::StringLiteral; static std::unique_ptr> jvm; // Generic method test suitable for simple primitive types. // Strings are passed through lambdas as method indexing is compile time. -template +template void GenericMethodTest( - MethodNameLambda method_name_lambda, LocalArray arr, + LocalArray arr, SpanType max_val = std::numeric_limits::max()) { // Simple lvalue pass through works as expected. - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{1}, arr); + StaticRef{}.Call( + SpanType{0}, SpanType{1}, arr); // Simple rvalue pass through works as expected. - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{1}, std::move(arr)); + StaticRef{}.Call( + SpanType{0}, SpanType{1}, std::move(arr)); // Building a new array, and setting all the values by hand works. LocalArray new_array{3}; @@ -69,8 +69,8 @@ void GenericMethodTest( new_array.Set(1, row2); new_array.Set(2, row3); - StaticRef{}(method_name_lambda(), SpanType{0}, - SpanType{1}, new_array); + StaticRef{}.Call( + SpanType{0}, SpanType{1}, new_array); } // You can pull the view multiple times with iterators (each value ticked 1). @@ -80,8 +80,8 @@ void GenericMethodTest( val = Modulo(1, val, max_val); } } - StaticRef{}(method_name_lambda(), SpanType{1}, - SpanType{1}, new_array); + StaticRef{}.Call( + SpanType{1}, SpanType{1}, new_array); } // You can pull the view multiple times with raw loops. @@ -98,8 +98,8 @@ void GenericMethodTest( } // Each variant increments base by 1, so 2 is used here. - StaticRef{}( - method_name_lambda(), Modulo(2, {0}, max_val), SpanType{1}, new_array); + StaticRef{}.Call( + Modulo(2, {0}, max_val), SpanType{1}, new_array); } extern "C" { @@ -118,53 +118,53 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeBooleanTests2D( JNIEnv*, jclass, jobjectArray arr) { // Boolean has a max of "2" which makes the generic moduloing logic work. - GenericMethodTest(STR("assertBoolean2D"), LocalArray{arr}, - jboolean{2}); + GenericMethodTest(LocalArray{arr}, + jboolean{2}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeByteTests2D(JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertByte2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeCharTests2D(JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertChar2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeShortTests2D( JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertShort2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeIntTests2D(JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertInt2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeLongTests2D(JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertLong2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeFloatTests2D( JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertFloat2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL Java_com_jnibind_test_ArrayTestMethodRank2_nativeDoubleTests2D( JNIEnv*, jclass, jobjectArray arr) { - GenericMethodTest(STR("assertDouble2D"), LocalArray{arr}); + GenericMethodTest(LocalArray{arr}); } JNIEXPORT void JNICALL @@ -172,10 +172,11 @@ Java_com_jnibind_test_ArrayTestMethodRank2_nativeObjectTests2D( JNIEnv* env, jclass, jobjectArray arr_jobjectArray) { // Simple lvalue pass through works as expected. LocalArray arr{arr_jobjectArray}; - StaticRef{}("assertObject2D", 0, 1, arr); + StaticRef{}.Call<"assertObject2D">(0, 1, arr); // Simple rvalue pass through works as expected. - StaticRef{}("assertObject2D", 0, 1, std::move(arr)); + StaticRef{}.Call<"assertObject2D">(0, 1, + std::move(arr)); // Building a new array, and setting all the values by hand works. LocalArray new_array{3, nullptr}; @@ -199,18 +200,18 @@ Java_com_jnibind_test_ArrayTestMethodRank2_nativeObjectTests2D( new_array.Set(1, row2); new_array.Set(2, row3); - StaticRef{}("assertObject2D", 0, 1, new_array); + StaticRef{}.Call<"assertObject2D">(0, 1, new_array); // You can pull the view multiple times with iterators (each value ticked 1). { int i = 0; for (LocalArray inner_array : new_array.Pin()) { for (LocalObject obj : inner_array.Pin()) { - obj["intVal1"].Set(i); + obj.Access<"intVal1">().Set(i); i++; - obj["intVal2"].Set(i); + obj.Access<"intVal2">().Set(i); i++; - obj["intVal3"].Set(i); + obj.Access<"intVal3">().Set(i); i++; } } @@ -222,13 +223,13 @@ Java_com_jnibind_test_ArrayTestMethodRank2_nativeObjectTests2D( for (int i = 0; i < new_array.Length(); ++i) { LocalArray inn_arr{new_array.Get(i)}; for (int j = 0; j < inn_arr.Length(); ++j) { - inn_arr.Get(j)("increment", 1); + inn_arr.Get(j).Call<"increment">(1); } } } // Each variant increments base by 1, so 2 is used here. - StaticRef{}("assertObject2D", 1, 1, new_array); + StaticRef{}.Call<"assertObject2D">(1, 1, new_array); } } // extern "C" diff --git a/javatests/com/jnibind/test/builder_jni.cc b/javatests/com/jnibind/test/builder_jni.cc index a5719432..5f927494 100644 --- a/javatests/com/jnibind/test/builder_jni.cc +++ b/javatests/com/jnibind/test/builder_jni.cc @@ -56,8 +56,11 @@ JNI_BIND_EXPORT jint JNI_BIND_CALL JNI_OnLoad(JavaVM* pjvm, void* reserved) { JNI_MTHD(void, nativeJniTeardown) { jvm = nullptr; } JNI_MTHD(jobject, useBuilderToCreateObject) { - return LocalObject{}("setOne", 111)("setTwo", 222)("setThree", - 333)("build") + return LocalObject{} + .Call<"setOne">(111) + .Call<"setTwo">(222) + .Call<"setThree">(333) + .Call<"build">() .Release(); } diff --git a/javatests/com/jnibind/test/context_test_jni.cc b/javatests/com/jnibind/test/context_test_jni.cc index 0786406c..e7bc4db3 100644 --- a/javatests/com/jnibind/test/context_test_jni.cc +++ b/javatests/com/jnibind/test/context_test_jni.cc @@ -55,7 +55,7 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_ContextTest_DoSetup(JNIEnv* env, JNIEXPORT jlong JNICALL Java_com_jnibind_test_ContextTest_nativeCreateContext( JNIEnv* env, jclass, jint val) { auto* ctx_struct = new ContextStruct{GlobalObject{}}; - ctx_struct->obj["intVal1"].Set(jint{val}); + ctx_struct->obj.Access<"intVal1">().Set(jint{val}); return reinterpret_cast(ctx_struct); } diff --git a/javatests/com/jnibind/test/field_test_jni.cc b/javatests/com/jnibind/test/field_test_jni.cc index 349daa14..cfa09b08 100644 --- a/javatests/com/jnibind/test/field_test_jni.cc +++ b/javatests/com/jnibind/test/field_test_jni.cc @@ -60,33 +60,33 @@ Java_com_jnibind_test_FieldTest_jniTearDown(JavaVM* pjvm, void* reserved) { JNIEXPORT jint JNICALL Java_com_jnibind_test_FieldTest_jniIntField( JNIEnv* env, jclass, jobject object, jint val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["intField"].Set(jint{val}); + rjni_test_helper.Access<"intField">().Set(jint{val}); - return rjni_test_helper["intField"].Get(); + return rjni_test_helper.Access<"intField">().Get(); } JNIEXPORT jfloat JNICALL Java_com_jnibind_test_FieldTest_jniFloatField( JNIEnv* env, jclass, jobject object, jfloat val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["floatField"].Set(jfloat{val}); + rjni_test_helper.Access<"floatField">().Set(jfloat{val}); - return rjni_test_helper["floatField"].Get(); + return rjni_test_helper.Access<"floatField">().Get(); } JNIEXPORT jdouble JNICALL Java_com_jnibind_test_FieldTest_jniDoubleField( JNIEnv* env, jclass, jobject object, jdouble val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["doubleField"].Set(jdouble{val}); + rjni_test_helper.Access<"doubleField">().Set(jdouble{val}); - return rjni_test_helper["doubleField"].Get(); + return rjni_test_helper.Access<"doubleField">().Get(); } JNIEXPORT jstring JNICALL Java_com_jnibind_test_FieldTest_jniStringField( JNIEnv* env, jclass, jobject object, jstring val) { LocalObject rjni_test_helper{object}; - rjni_test_helper["stringField"].Set(val); + rjni_test_helper.Access<"stringField">().Set(val); - return rjni_test_helper["stringField"].Get().Release(); + return rjni_test_helper.Access<"stringField">().Get().Release(); } JNIEXPORT void JNICALL Java_com_jnibind_test_FieldTest_jniObjectFieldSet( @@ -95,12 +95,15 @@ JNIEXPORT void JNICALL Java_com_jnibind_test_FieldTest_jniObjectFieldSet( LocalObject field_test{test_class}; LocalObject obj{intVal, floatVal, doubleVal}; - field_test["fieldTestHelper"].Set(obj); + field_test.Access<"fieldTestHelper">().Set(obj); } JNIEXPORT jobject JNICALL Java_com_jnibind_test_FieldTest_jniObjectFieldGet( JNIEnv* env, jclass, jobject test_class) { - return LocalObject{test_class}["fieldTestHelper"].Get().Release(); + return LocalObject{test_class} + .Access<"fieldTestHelper">() + .Get() + .Release(); } } // extern "C" diff --git a/javatests/com/jnibind/test/global_object_test_jni.cc b/javatests/com/jnibind/test/global_object_test_jni.cc index ee27b44d..97fe83b5 100644 --- a/javatests/com/jnibind/test/global_object_test_jni.cc +++ b/javatests/com/jnibind/test/global_object_test_jni.cc @@ -63,8 +63,8 @@ Java_com_jnibind_test_GlobalObjectTest_jniBuildNewObjectsFromExistingObjects( JNIEnv* env, jclass, jobject test_helper_object, jobject object_to_mutate) { jni::LocalObject helper_obj{object_to_mutate}; - return jni::LocalObject{test_helper_object}( - "methodTakesGlobalObjectReturnsNewObject", helper_obj) + return jni::LocalObject{test_helper_object} + .Call<"methodTakesGlobalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -73,17 +73,17 @@ Java_com_jnibind_test_GlobalObjectTest_jniManipulateNewGlobalObjectSetIntVal238( JNIEnv* env, jclass, jobject jtest_obj) { jni::GlobalObject helper_obj{2, 3, 8}; - return jni::LocalObject{jtest_obj}( - "methodTakesGlobalObjectReturnsNewObject", std::move(helper_obj)) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesGlobalObjectReturnsNewObject">(std::move(helper_obj)) .Release(); } JNIEXPORT jobject JNICALL Java_com_jnibind_test_GlobalObjectTest_jniMaterializeNewGlobalObjectSetIntVal159( JNIEnv* env, jclass, jobject jtest_obj) { - return jni::LocalObject{jtest_obj}( - "methodTakesGlobalObjectReturnsNewObject", - jni::GlobalObject{1, 5, 9}) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesGlobalObjectReturnsNewObject">( + jni::GlobalObject{1, 5, 9}) .Release(); } diff --git a/javatests/com/jnibind/test/local_object_test_jni.cc b/javatests/com/jnibind/test/local_object_test_jni.cc index f2cf225e..fda520b4 100644 --- a/javatests/com/jnibind/test/local_object_test_jni.cc +++ b/javatests/com/jnibind/test/local_object_test_jni.cc @@ -65,8 +65,8 @@ Java_com_jnibind_test_LocalObjectTest_jniBuildNewObjectsFromExistingObjects( JNIEnv* env, jclass, jobject jtest_obj, jobject jhelper_obj) { jni::LocalObject helper_obj{jhelper_obj}; - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", helper_obj) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(helper_obj) .Release(); } @@ -75,17 +75,17 @@ Java_com_jnibind_test_LocalObjectTest_jniManipulateNewLocalObjectSetIntVal238( JNIEnv* env, jclass, jobject jtest_obj) { jni::LocalObject helper_obj{2, 3, 8}; - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", std::move(helper_obj)) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">(std::move(helper_obj)) .Release(); } JNIEXPORT jobject JNICALL Java_com_jnibind_test_LocalObjectTest_jniMaterializeNewLocalObjectSetIntVal159( JNIEnv* env, jclass, jobject jtest_obj) { - return jni::LocalObject{jtest_obj}( - "methodTakesLocalObjectReturnsNewObject", - jni::LocalObject{1, 5, 9}) + return jni::LocalObject{jtest_obj} + .Call<"methodTakesLocalObjectReturnsNewObject">( + jni::LocalObject{1, 5, 9}) .Release(); } diff --git a/javatests/com/jnibind/test/method_test_jni.cc b/javatests/com/jnibind/test/method_test_jni.cc index f298c5b3..51928989 100644 --- a/javatests/com/jnibind/test/method_test_jni.cc +++ b/javatests/com/jnibind/test/method_test_jni.cc @@ -92,7 +92,7 @@ Java_com_jnibind_test_MethodTest_jniTearDown(JavaVM* pjvm, void* reserved) { JNIEXPORT void JNICALL Java_com_jnibind_test_MethodTest_jniVoidMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - gtest_helper("voidMethod"); + gtest_helper.Call<"voidMethod">(); } JNIEXPORT void JNICALL @@ -100,7 +100,7 @@ Java_com_jnibind_test_MethodTest_jniVoidMethodTakesOneInt(JNIEnv* env, jclass, jobject object, jint i) { LocalObject gtest_helper{object}; - gtest_helper("voidMethodTakesOneInt", jint{i}); + gtest_helper.Call<"voidMethodTakesOneInt">(jint{i}); } JNIEXPORT void JNICALL @@ -110,8 +110,8 @@ Java_com_jnibind_test_MethodTest_jniVoidMethodTakesFiveInts(JNIEnv* env, jclass, jint i3, jint i4, jint i5) { LocalObject gtest_helper{object}; - gtest_helper("voidMethodTakesFiveInts", jint{i1}, jint{i2}, jint{i3}, - jint{i4}, jint{i5}); + gtest_helper.Call<"voidMethodTakesFiveInts">(jint{i1}, jint{i2}, jint{i3}, + jint{i4}, jint{i5}); } /** Boolean Method Tests. */ @@ -119,7 +119,7 @@ JNIEXPORT jboolean JNICALL Java_com_jnibind_test_MethodTest_jniBooleanMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("booleanMethod"); + return gtest_helper.Call<"booleanMethod">(); } JNIEXPORT jboolean JNICALL @@ -128,7 +128,7 @@ Java_com_jnibind_test_MethodTest_jniBooleanMethodTakesOneBoolean(JNIEnv* env, jobject object, jboolean i) { LocalObject gtest_helper{object}; - return gtest_helper("booleanMethodTakesOneBoolean", jboolean{i}); + return gtest_helper.Call<"booleanMethodTakesOneBoolean">(jboolean{i}); } JNIEXPORT jboolean JNICALL @@ -137,8 +137,8 @@ Java_com_jnibind_test_MethodTest_jniBooleanMethodTakesFiveBooleans( jboolean i4, jboolean i5) { LocalObject gtest_helper{object}; - return gtest_helper("booleanMethodTakesFiveBooleans", jboolean{i1}, - jboolean{i2}, jboolean{i3}, jboolean{i4}, jboolean{i5}); + return gtest_helper.Call<"booleanMethodTakesFiveBooleans">( + jboolean{i1}, jboolean{i2}, jboolean{i3}, jboolean{i4}, jboolean{i5}); } /** Int Method Tests. */ @@ -146,14 +146,14 @@ JNIEXPORT jint JNICALL Java_com_jnibind_test_MethodTest_jniIntMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("intMethod"); + return gtest_helper.Call<"intMethod">(); } JNIEXPORT jint JNICALL Java_com_jnibind_test_MethodTest_jniIntMethodTakesOneInt( JNIEnv* env, jclass, jobject object, jint i) { LocalObject gtest_helper{object}; - return gtest_helper("intMethodTakesOneInt", jint{i}); + return gtest_helper.Call<"intMethodTakesOneInt">(jint{i}); } JNIEXPORT jint JNICALL @@ -164,8 +164,8 @@ Java_com_jnibind_test_MethodTest_jniIntMethodTakesFiveInts(JNIEnv* env, jclass, jint i5) { LocalObject gtest_helper{object}; - return gtest_helper("intMethodTakesFiveInts", jint{i1}, jint{i2}, jint{i3}, - jint{i4}, jint{i5}); + return gtest_helper.Call<"intMethodTakesFiveInts">( + jint{i1}, jint{i2}, jint{i3}, jint{i4}, jint{i5}); } /** Long Method Tests. */ @@ -173,7 +173,7 @@ JNIEXPORT jlong JNICALL Java_com_jnibind_test_MethodTest_jniLongMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("longMethod"); + return gtest_helper.Call<"longMethod">(); } JNIEXPORT jlong JNICALL @@ -182,7 +182,7 @@ Java_com_jnibind_test_MethodTest_jniLongMethodTakesOneLong(JNIEnv* env, jclass, jlong i) { LocalObject gtest_helper{object}; - return gtest_helper("longMethodTakesOneLong", jlong{i}); + return gtest_helper.Call<"longMethodTakesOneLong">(jlong{i}); } JNIEXPORT jlong JNICALL @@ -191,8 +191,8 @@ Java_com_jnibind_test_MethodTest_jniLongMethodTakesFiveLongs( jlong i5) { LocalObject gtest_helper{object}; - return gtest_helper("longMethodTakesFiveLongs", jlong{i1}, jlong{i2}, - jlong{i3}, jlong{i4}, jlong{i5}); + return gtest_helper.Call<"longMethodTakesFiveLongs">( + jlong{i1}, jlong{i2}, jlong{i3}, jlong{i4}, jlong{i5}); } /** Float Method Tests. */ @@ -200,7 +200,7 @@ JNIEXPORT jfloat JNICALL Java_com_jnibind_test_MethodTest_jniFloatMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("floatMethod"); + return gtest_helper.Call<"floatMethod">(); } JNIEXPORT jfloat JNICALL @@ -210,7 +210,7 @@ Java_com_jnibind_test_MethodTest_jniFloatMethodTakesOneFloat(JNIEnv* env, jfloat i) { LocalObject gtest_helper{object}; - return gtest_helper("floatMethodTakesOneFloat", jfloat{i}); + return gtest_helper.Call<"floatMethodTakesOneFloat">(jfloat{i}); } JNIEXPORT jfloat JNICALL @@ -219,8 +219,8 @@ Java_com_jnibind_test_MethodTest_jniFloatMethodTakesFiveFloats( jfloat i4, jfloat i5) { LocalObject gtest_helper{object}; - return gtest_helper("floatMethodTakesFiveFloats", jfloat{i1}, jfloat{i2}, - jfloat{i3}, jfloat{i4}, jfloat{i5}); + return gtest_helper.Call<"floatMethodTakesFiveFloats">( + jfloat{i1}, jfloat{i2}, jfloat{i3}, jfloat{i4}, jfloat{i5}); } /** Double Method Tests. */ @@ -228,7 +228,7 @@ JNIEXPORT jdouble JNICALL Java_com_jnibind_test_MethodTest_jniDoubleMethod( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("doubleMethod"); + return gtest_helper.Call<"doubleMethod">(); } JNIEXPORT jdouble JNICALL @@ -238,7 +238,7 @@ Java_com_jnibind_test_MethodTest_jniDoubleMethodTakesOneDouble(JNIEnv* env, jdouble i) { LocalObject gtest_helper{object}; - return gtest_helper("doubleMethodTakesOneDouble", jdouble{i}); + return gtest_helper.Call<"doubleMethodTakesOneDouble">(jdouble{i}); } JNIEXPORT jdouble JNICALL @@ -247,8 +247,8 @@ Java_com_jnibind_test_MethodTest_jniDoubleMethodTakesFiveDoubles( jdouble i4, jdouble i5) { LocalObject gtest_helper{object}; - return gtest_helper("doubleMethodTakesFiveDoubles", jdouble{i1}, jdouble{i2}, - jdouble{i3}, jdouble{i4}, jdouble{i5}); + return gtest_helper.Call<"doubleMethodTakesFiveDoubles">( + jdouble{i1}, jdouble{i2}, jdouble{i3}, jdouble{i4}, jdouble{i5}); } /** Overload Method Tests. */ @@ -256,29 +256,29 @@ JNIEXPORT int JNICALL Java_com_jnibind_test_MethodTest_jniCallFooOverload1( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("foo"); + return gtest_helper.Call<"foo">(); } JNIEXPORT int JNICALL Java_com_jnibind_test_MethodTest_jniCallFooOverload2( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("foo", 123.f); + return gtest_helper.Call<"foo">(123.f); } JNIEXPORT int JNICALL Java_com_jnibind_test_MethodTest_jniCallFooOverload3( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - return gtest_helper("foo", 123.f, 456.f); + return gtest_helper.Call<"foo">(123.f, 456.f); } /** Overload int/double disambiguation. */ JNIEXPORT void JNICALL Java_com_jnibind_test_MethodTest_jniCallFooOverload4( JNIEnv* env, jclass, jobject object) { LocalObject gtest_helper{object}; - gtest_helper("intDouble", 123, 456.); - gtest_helper("intDouble", 456., 123); + gtest_helper.Call<"intDouble">(123, 456.); + gtest_helper.Call<"intDouble">(456., 123); } } // extern "C" diff --git a/javatests/com/jnibind/test/static_test_jni.cc b/javatests/com/jnibind/test/static_test_jni.cc index 26260150..558bdcda 100644 --- a/javatests/com/jnibind/test/static_test_jni.cc +++ b/javatests/com/jnibind/test/static_test_jni.cc @@ -73,49 +73,49 @@ Java_com_jnibind_test_StaticTest_jniTearDown(JavaVM* pjvm, void* reserved) { //////////////////////////////////////////////////////////////////////////////// JNIEXPORT void JNICALL Java_com_jnibind_test_StaticTest_voidMethodTestNative( JavaVM* pjvm, void* reserved) { - StaticRef{}("voidFunc"); + StaticRef{}.Call<"voidFunc">(); } JNIEXPORT jbyte JNICALL Java_com_jnibind_test_StaticTest_byteMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("byteFunc"); + return StaticRef{}.Call<"byteFunc">(); } JNIEXPORT jchar JNICALL Java_com_jnibind_test_StaticTest_charMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("charFunc"); + return StaticRef{}.Call<"charFunc">(); } JNIEXPORT jshort JNICALL Java_com_jnibind_test_StaticTest_shortMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("shortFunc"); + return StaticRef{}.Call<"shortFunc">(); } JNIEXPORT jint JNICALL Java_com_jnibind_test_StaticTest_intMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("intFunc"); + return StaticRef{}.Call<"intFunc">(); } JNIEXPORT jlong JNICALL Java_com_jnibind_test_StaticTest_longMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("longFunc"); + return StaticRef{}.Call<"longFunc">(); } JNIEXPORT jfloat JNICALL Java_com_jnibind_test_StaticTest_floatMethodTestNative( JavaVM* pjvm, void* reserved) { - return StaticRef{}("floatFunc"); + return StaticRef{}.Call<"floatFunc">(); } JNIEXPORT jdouble JNICALL Java_com_jnibind_test_StaticTest_doubleMethodTestNative(JavaVM* pjvm, void* reserved) { - return StaticRef{}("doubleFunc"); + return StaticRef{}.Call<"doubleFunc">(); } JNIEXPORT jobject JNICALL Java_com_jnibind_test_StaticTest_objectMethodTestNative(JavaVM* pjvm, void* reserved) { - return StaticRef{}("objectFunc").Release(); + return StaticRef{}.Call<"objectFunc">().Release(); } JNIEXPORT jstring JNICALL @@ -123,7 +123,7 @@ Java_com_jnibind_test_StaticTest_complexMethodTestNative(JavaVM* pjvm, void* reserved, jint a, jfloat b, jstring c, jobjectArray d) { - return StaticRef{}("complexFunc", a, b, c, d).Release(); + return StaticRef{}.Call<"complexFunc">(a, b, c, d).Release(); } //////////////////////////////////////////////////////////////////////////////// @@ -131,57 +131,57 @@ Java_com_jnibind_test_StaticTest_complexMethodTestNative(JavaVM* pjvm, //////////////////////////////////////////////////////////////////////////////// JNIEXPORT bool JNICALL Java_com_jnibind_test_StaticTest_booleanFieldTestNative( JavaVM* pjvm, void* reserved, bool val) { - StaticRef{}["booleanField"].Set(val); - return StaticRef{}["booleanField"].Get(); + StaticRef{}.Access<"booleanField">().Set(val); + return StaticRef{}.Access<"booleanField">().Get(); } JNIEXPORT jbyte JNICALL Java_com_jnibind_test_StaticTest_byteFieldTestNative( JavaVM* pjvm, void* reserved, jbyte val) { - StaticRef{}["byteField"].Set(val); - return StaticRef{}["byteField"].Get(); + StaticRef{}.Access<"byteField">().Set(val); + return StaticRef{}.Access<"byteField">().Get(); } JNIEXPORT char JNICALL Java_com_jnibind_test_StaticTest_charFieldTestNative( JavaVM* pjvm, void* reserved, jchar val) { - StaticRef{}["charField"].Set(val); - return StaticRef{}["charField"].Get(); + StaticRef{}.Access<"charField">().Set(val); + return StaticRef{}.Access<"charField">().Get(); } JNIEXPORT short JNICALL Java_com_jnibind_test_StaticTest_shortFieldTestNative( JavaVM* pjvm, void* reserved, jshort val) { - StaticRef{}["shortField"].Set(val); - return StaticRef{}["shortField"].Get(); + StaticRef{}.Access<"shortField">().Set(val); + return StaticRef{}.Access<"shortField">().Get(); } JNIEXPORT int JNICALL Java_com_jnibind_test_StaticTest_intFieldTestNative( JavaVM* pjvm, void* reserved, jint val) { - StaticRef{}["intField"].Set(val); - return StaticRef{}["intField"].Get(); + StaticRef{}.Access<"intField">().Set(val); + return StaticRef{}.Access<"intField">().Get(); } JNIEXPORT long JNICALL Java_com_jnibind_test_StaticTest_longFieldTestNative( JavaVM* pjvm, void* reserved, jlong val) { - StaticRef{}["longField"].Set(val); - return StaticRef{}["longField"].Get(); + StaticRef{}.Access<"longField">().Set(val); + return StaticRef{}.Access<"longField">().Get(); } JNIEXPORT float JNICALL Java_com_jnibind_test_StaticTest_floatFieldTestNative( JavaVM* pjvm, void* reserved, jfloat val) { - StaticRef{}["floatField"].Set(val); - return StaticRef{}["floatField"].Get(); + StaticRef{}.Access<"floatField">().Set(val); + return StaticRef{}.Access<"floatField">().Get(); } JNIEXPORT double JNICALL Java_com_jnibind_test_StaticTest_doubleFieldTestNative( JavaVM* pjvm, void* reserved, jdouble val) { - StaticRef{}["doubleField"].Set(val); - return StaticRef{}["doubleField"].Get(); + StaticRef{}.Access<"doubleField">().Set(val); + return StaticRef{}.Access<"doubleField">().Get(); } JNIEXPORT jobject JNICALL Java_com_jnibind_test_StaticTest_objectFieldTestNative(JavaVM* pjvm, void* reserved, jobject val) { - StaticRef{}["objectField"].Set(val); - return StaticRef{}["objectField"].Get().Release(); + StaticRef{}.Access<"objectField">().Set(val); + return StaticRef{}.Access<"objectField">().Get().Release(); } } diff --git a/javatests/com/jnibind/test/thread_test_jni.cc b/javatests/com/jnibind/test/thread_test_jni.cc index 4ea7d098..f1d6072e 100644 --- a/javatests/com/jnibind/test/thread_test_jni.cc +++ b/javatests/com/jnibind/test/thread_test_jni.cc @@ -57,7 +57,7 @@ Java_com_jnibind_test_ThreadTest_RunsThreadedWorkOnObject(JNIEnv* env, jclass, std::thread worker{ [global_object_lambda_scope{std::move(global_obj)}]() mutable { ThreadGuard thread_guard{}; - global_object_lambda_scope("foo"); + global_object_lambda_scope.Call<"foo">(); }}; worker.join(); diff --git a/jni_bind.h b/jni_bind.h index 6c4c79c3..dfaca903 100644 --- a/jni_bind.h +++ b/jni_bind.h @@ -50,6 +50,7 @@ static constexpr Configuration kConfig{ #include "implementation/jni_helper/jni_helper.h" #include "implementation/jni_helper/lifecycle.h" #include "implementation/jni_helper/static_field_value.h" +#include "metaprogramming/string_literal.h" // Headers for static definitions. #include "implementation/array.h" diff --git a/jni_bind_release.h b/jni_bind_release.h index 13869554..c9a4d8ce 100644 --- a/jni_bind_release.h +++ b/jni_bind_release.h @@ -462,7 +462,7 @@ struct Return : ReturnBase { using Raw = Raw_; - constexpr Return() = default; + constexpr Return() {} template constexpr explicit Return(Raw raw) : raw_(raw) {} @@ -473,7 +473,7 @@ struct Return : ReturnBase { using Raw = void; const Void raw_{}; - constexpr Return() = default; + constexpr Return() {} }; Return() -> Return; @@ -1912,7 +1912,10 @@ namespace jni { template -struct Class {}; +struct Class { + constexpr Class() = default; + constexpr Class(const char* name) {} +}; template , // provided where they are and aren't present. //////////////////////////////////////////////////////////////////////////////// + // To stifle a test failure. + constexpr Class() + : Object("__JNI_BIND__NO_CLASS__"), + constructors_(Constructor<>{}), + static_(), + methods_(), + fields_() {} + // Methods + Fields. explicit constexpr Class(const char* class_name, Methods_... methods, Fields_... fields) @@ -2501,6 +2512,8 @@ namespace jni { inline constexpr Class kJavaLangClass{ "java/lang/Class", Method{"getClassLoader", Return{ Class { "java/lang/ClassLoader" } }}, + Method{"toString", Return{jstring{}}, Params<>{}}, + Method{"getName", Return{jstring{}}, Params<>{}} }; inline constexpr Class kJavaLangObject{ @@ -5746,6 +5759,52 @@ using TupleFromSize_t = decltype(TupleFromSize()); } // namespace jni::metaprogramming +#ifdef __cplusplus +#if __cplusplus >= 202002L + +#include +#include +#include + +namespace jni::metaprogramming { + +// Inspired by Kevin Hartman's StringLiteral implementation. +// https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/ +// +// This class is not currently being used, but is being used to prototype +// changes that will be needed for consumers of `InvocableMap`. This class is +// not included by default for build because it requires C++20. +template +struct StringLiteral { + constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); } + + constexpr StringLiteral Self() { return *this; } + + template + constexpr bool operator==(const StringLiteral& rhs) const { + if constexpr (N != U) { + return false; + } else { + return std::string_view{value} == std::string_view{rhs.value}; + } + } + + template + constexpr bool operator!=(const StringLiteral& rhs) const { + return !(*this == rhs); + } + + char value[N]; +}; + +template +StringLiteral(const char (&str)[N]) -> StringLiteral; + +} // namespace jni::metaprogramming + +#endif // __cplusplus >= 202002L +#endif // __cplusplus + #include namespace jni::metaprogramming { @@ -6367,6 +6426,75 @@ static constexpr bool StringContains_v = namespace jni::metaprogramming { +// This is an interface that can be inherited from to expose an +// operator.Access<"name">(). It provides compile time string index lookup with +// no macros although it is dependent on a clang extension. +// +// To use this API, inherit from this class using template types as follows: +// +// |CrtpBase|: The name of the class inheriting from the map. This class +// will inherit an operator[]. It must implement this exact signature: +// +// template +// auto QueryableMap20Call(); +// +// |tup_container_v| is a static instance of an object whose |nameable_member| +// contains a public field called name_. It might seem strange not to +// directly pass a const auto&, however, this prevents accessing subobjects. +// +// The motivation for using inheritance as opposed to a simple member is that +// the the const char cannot be propagated without losing its constexpr-ness, +// and so the clang extension can no longer restrict function candidates. +template +class QueryableMap20 { +#if __cplusplus >= 202002L + private: + template + constexpr auto Do() { + return (*static_cast(this)) + .template QueryableMap20Call(); + } + + template + static constexpr std::size_t SelectCandidate(StringLiteral string_literal, + std::index_sequence) { + return ModifiedMax( + {((std::get(tup_container_v.*nameable_member).name_ == + std::string_view{string_literal.value}) + ? std::size_t{Is} + : kNegativeOne)..., + kNegativeOne}); + } + + public: + template + constexpr auto Access() { + return Do>>())>(); + } +#endif // __cplusplus >= 202002L +}; + +template ::* nameable_member> +using QueryableMap20_t = + QueryableMap20, nameable_member>; + +} // namespace jni::metaprogramming + +#include +#include +#include +#include +#include + +namespace jni::metaprogramming { + template @@ -6557,6 +6685,83 @@ struct PackDiscriminatedForward { namespace jni::metaprogramming { +// This class enables compile time lookup that perfectly forward arguments +// to a method named `Call`. This is a C++ 20 version of InvocableMap. +// +// This is an interface that can be inherited from to expose a method named +// `Call`. It provides compile time string index lookup with no macros. +// +// To use this API, inherit from this class using template types as follows: +// +// |CrtpBase|: The name of the class inheriting from the map. This class +// will inherit an operator(). It must implement this exact signature: +// +// template +// auto InvocableMap20Call(Args&&... args); +// +// If i is the index where |tup_container_v.*nameable_member|.name_ == key, +// then InvocableMap20Call will forward the args from operator() with the +// same args. Static memory can be used in this function call and it will +// be unique because of the I non-type template parameter. +// +// |tup_container_v| is a static instance of an object whose |nameable_member| +// contains a public field called name_. It might seem strange not to +// directly pass a const auto&, however, this prevents accessing subobjects. +// +// The motivation for using inheritance as opposed to a simple member is that +// the the const char cannot be propagated without losing its constexpr-ness, +// and so the clang extension can no longer restrict function candidates. +template +class InvocableMap20 { +#if __cplusplus >= 202002L + public: + template + constexpr auto Do(Args&&... args) { + return (*static_cast(this)) + .template InvocableMap20Call( + std::forward(args)...); + } + + template + static constexpr std::size_t SelectCandidate(StringLiteral string_literal, + std::index_sequence) { + return ModifiedMax( + {((std::get(tup_container_v.*nameable_member).name_ == + std::string_view{string_literal.value}) + ? std::size_t{Is} + : kNegativeOne)..., + kNegativeOne}); + } + + template + constexpr auto Call(Args&&... args) { + return Do>>())>( + std::forward(args)...); + } +#endif // __cplusplus >= 202002L +}; + +template ::* nameable_member> +using InvocableMap20_t = + InvocableMap20, nameable_member>; + +} // namespace jni::metaprogramming + +#include +#include +#include +#include +#include + +namespace jni::metaprogramming { + template class InvocableMap; @@ -6574,7 +6779,7 @@ class InvocableMap; // auto InvocableMapCall(const char* key, Args&&... args); // // If i is the index where |tup_container_v.*nameable_member|.name_ == key, -// then InvocablemapCall will forward the args from operator() with the +// then InvocableMapCall will forward the args from operator() with the // same args. Static memory can be used in this function call and it will // be unique because of the I non-type template parameter. // @@ -7101,11 +7306,17 @@ namespace jni { // operator[]. template class ObjectRef + // C++17 augmentations. : public metaprogramming::InvocableMap< ObjectRef, JniT::stripped_class_v, typename JniT::ClassT, &JniT::ClassT::methods_>, public metaprogramming::QueryableMap_t< ObjectRef, JniT::stripped_class_v, &JniT::ClassT::fields_>, + // C++ 20 augmentations. + public metaprogramming::InvocableMap20_t< + ObjectRef, JniT::stripped_class_v, &JniT::ClassT::methods_>, + public metaprogramming::QueryableMap20_t< + ObjectRef, JniT::stripped_class_v, &JniT::ClassT::fields_>, public RefBase { protected: static_assert( @@ -7137,6 +7348,12 @@ class ObjectRef explicit ObjectRef(RefBaseT&& rhs) : RefBaseT(std::move(rhs)) {} + //////////////////////////////////////////////////////////////////////////////// + // Implementation: C++17 + clang + // Supports syntax like: "obj("foo", 123, args)", "obj["foo"].Get()" + // This syntax is less portable and may be removed in a major future release. + //////////////////////////////////////////////////////////////////////////////// +#if __clang__ // Invoked through CRTP from InvocableMap. template auto InvocableMapCall(const char* key, Args&&... args) const { @@ -7157,10 +7374,41 @@ class ObjectRef auto QueryableMapCall(const char* key) const { return FieldRef{GetJClass(), RefBaseT::object_ref_}; } +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// C++20 Implementation (will only be included if supported). +// Supports syntax like: "obj.Call<"foo">(123, args)", +// "obj.Access<"foo">().Get()" Prefer using this syntax as they are more +// portable. +//////////////////////////////////////////////////////////////////////////////// +#if __cplusplus >= 202002L + // Invoked through CRTP from InvocableMap, C++20 only. + template + auto InvocableMap20Call(Args&&... args) const { + using IdT = Id; + using MethodSelectionForArgs = + OverloadSelector; + + static_assert(MethodSelectionForArgs::kIsValidArgSet, + "JNI Error: Invalid argument set."); + + return MethodSelectionForArgs::OverloadRef::Invoke( + GetJClass(), RefBaseT::object_ref_, std::forward(args)...); + } + + // Invoked through CRTP from QueryableMap20, C++20 only. + template + auto QueryableMap20Call() const { + return FieldRef{GetJClass(), RefBaseT::object_ref_}; + } +#endif // __cplusplus >= 202002L }; // Imbues constructors for ObjectRefs and handles calling the correct -// intermediate constructors. Access to this class is constrainted for non +// intermediate constructors. Access to this class is constrained for non // default classloaders (see |ValidatorProxy|). template class ConstructorValidator : public ObjectRef { @@ -8328,7 +8576,7 @@ class ClassLoaderRef : public ClassLoaderImpl { if constexpr (ParentLoaderForClass() != kDefaultClassLoader) { ClassRef_t>::PrimeJClassFromClassLoader([=]() { + 0>>::PrimeJClassFromClassLoader([&]() { // Prevent the object (which is a runtime instance of a class) from // falling out of scope so it is not released. LocalObject loaded_class = @@ -8519,1209 +8767,1209 @@ class ArrayRef 1)>> } // namespace jni -#include -#include -#include +// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" namespace jni { -//////////////////////////////////////////////////////////////////////////////// -// Rank 0 type (aka void). -//////////////////////////////////////////////////////////////////////////////// -template <> -struct InvokeHelper { - template - static void Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticVoidMethod")), clazz, - method_id, ts...); - -#ifdef DRY_RUN -#else - jni::JniEnv::GetEnv()->CallStaticVoidMethod(clazz, method_id, - std::forward(ts)...); -#endif // DRY_RUN - } -}; +// Designed to be the very last JniBind object to execute on the thread. +// Objects passed by move for lambdas will be destructed after any contents +// statements within their lambda, and `ThreadGuard` can't be moved into the +// lambda because its construction will be on the host thread. This static +// teardown guarantees a delayed destruction beyond any GlobalObject. +class ThreadLocalGuardDestructor { + public: + bool detach_thread_when_all_guards_released_ = false; -//////////////////////////////////////////////////////////////////////////////// -// Rank 0 types, i.e. the primitive type itself (e.g. int). -//////////////////////////////////////////////////////////////////////////////// -template <> -struct InvokeHelper { - template - static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticBooleanMethod")), clazz, - method_id, ts...); + // By calling this the compiler is obligated to perform initalisation. + void ForceDestructionOnThreadClose() {} -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticBooleanMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN + ~ThreadLocalGuardDestructor() { + if (detach_thread_when_all_guards_released_) { + JavaVM* jvm = JvmRefBase::GetJavaVm(); + if (jvm) { + jvm->DetachCurrentThread(); + } + } } }; -template <> -struct InvokeHelper { - template - static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticByteMethod")), clazz, - method_id, ts...); +// ThreadGuard attaches and detaches JNIEnv* objects on the creation of new +// threads. All new threads which want to use JNI Wrapper must hold a +// ThreadGuard beyond the scope of all created objects. If the ThreadGuard +// needs to create an Env, it will also detach itself. +class ThreadGuard { + public: + ~ThreadGuard() { thread_guard_count_--; } -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticByteMethod(clazz, method_id, - std::forward(ts)...); -#endif // DRY_RUN - } -}; + ThreadGuard(ThreadGuard&) = delete; + ThreadGuard(ThreadGuard&&) = delete; -template <> -struct InvokeHelper { - template - static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticCharMethod")), clazz, - method_id, ts...); + template + friend class JvmRef; -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticCharMethod(clazz, method_id, - std::forward(ts)...); -#endif // DRY_RUN - } -}; + // This constructor must *never* be called before a |JvmRef| has been + // constructed. It depends on static setup from |JvmRef|. + [[nodiscard]] ThreadGuard() { + thread_local_guard_destructor.ForceDestructionOnThreadClose(); -template <> -struct InvokeHelper { - template - static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticShortMethod")), clazz, - method_id, ts...); + // Nested ThreadGuards should be permitted in the same way mutex locks are. + thread_guard_count_++; + if (thread_guard_count_ != 1) { + // SetEnv has been called prior, GetEnv is currently valid. + return; + } -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticShortMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN - } -}; + // Declarations for AttachCurrentThread are inconsistent across different + // JNI headers. This forces a cast to whatever the expected type is. + JavaVM* const vm = JvmRefBase::GetJavaVm(); + JNIEnv* jni_env = 0; -template <> -struct InvokeHelper { - template - static jint Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticIntMethod")), clazz, - method_id, ts...); + using TypeForGetEnv = + metaprogramming::FunctionTraitsArg_t; + const int code = + vm->GetEnv(reinterpret_cast(&jni_env), JNI_VERSION_1_6); -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticIntMethod(clazz, method_id, - std::forward(ts)...); -#endif // DRY_RUN + if (code != JNI_OK) { + using TypeForAttachment = metaprogramming::FunctionTraitsArg_t< + decltype(&JavaVM::AttachCurrentThread), 1>; + vm->AttachCurrentThread(reinterpret_cast(&jni_env), + nullptr); + thread_local_guard_destructor.detach_thread_when_all_guards_released_ = + true; + } + // Why not store this locally to ThreadGuard? + // + // JNIEnv is thread local static, and the context an object is built from + // may not have easy access to a JNIEnv* (or this ThreadGuard). For most + // constructions of new objects, the env is likely trivial (it's passed as + // part of the JNI call), however, if an object reference is moved from one + // thread to another, the JNIEnv* is certainly not available. + JniEnv::SetEnv(jni_env); } + + private: + static inline thread_local int thread_guard_count_ = 0; + static inline thread_local ThreadLocalGuardDestructor + thread_local_guard_destructor{}; }; -template <> -struct InvokeHelper { - template - static jlong Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticLongMethod")), clazz, - method_id, ts...); +} // namespace jni -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticLongMethod(clazz, method_id, - std::forward(ts)...); -#endif // DRY_RUN - } -}; +// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" -template <> -struct InvokeHelper { - template - static jfloat Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticFloatMethod")), clazz, - method_id, ts...); +namespace jni { -#ifdef DRY_RUN - return 123.f; -#else - return jni::JniEnv::GetEnv()->CallStaticFloatMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN - } -}; +using LocalStringImpl = + Scoped, jobject, + jstring>; -template <> -struct InvokeHelper { - template - static jdouble Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticDoubleMethod")), clazz, - method_id, ts...); +// Represents and possibly builds a runtime Java String object. +// +// In order to use a string in memory (as opposed to only using it for function +// arguments), "Pin" the string. +// +// Like |jobjects|, |jstring|s can be either local or global with the same +// ownership semantics. +class LocalString : public LocalStringImpl { + public: + using Base = LocalStringImpl; + using Base::Base; -#ifdef DRY_RUN - return 123.; -#else - return jni::JniEnv::GetEnv()->CallStaticDoubleMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN - } -}; + LocalString(LocalObject&& obj) + : Base(AdoptLocal{}, static_cast(obj.Release())) {} -template <> -struct InvokeHelper { - // This always returns a local reference which should be embedded in type - // information wherever this is used. - template - static jobject Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod")), clazz, - method_id, ts...); + template + LocalString(ArrayViewHelper array_view_helper) + : LocalString(AdoptLocal{}, array_view_helper.val_) {} -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticObjectMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN - } + // Returns a StringView which possibly performs an expensive pinning + // operation. String objects can be pinned multiple times. + UtfStringView Pin() { return {RefBase::object_ref_}; } }; -template <> -struct InvokeHelper { - template - static jobject Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod")), clazz, - method_id, ts...); +} // namespace jni -#ifdef DRY_RUN - return Fake(); -#else - return jni::JniEnv::GetEnv()->CallStaticObjectMethod( - clazz, method_id, std::forward(ts)...); -#endif // DRY_RUN - } -}; +// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" -//////////////////////////////////////////////////////////////////////////////// -// Rank 1 types, i.e. single dimension arrays (e.g. int[]). -//////////////////////////////////////////////////////////////////////////////// -template -struct InvokeHelper, kRank, true> { - template - static jbooleanArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +#include +#include -#ifdef DRY_RUN - return Fake(); -#else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); -#endif // DRY_RUN - } -}; +namespace jni { -template -struct InvokeHelper, kRank, true> { - template - static jbyteArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +// Represents a an array object (e.g. int[], float[][], Object[], etc). +// Currently GlobalArrays do not exist, as reasoning about the lifecycles of the +// underlying objects is non-trivial, e.g. a GlobalArray taking a local object +// would result in a possibly unexpected extension of lifetime. +template +class LocalArray + : public ArrayRef< + JniT> { + public: + static constexpr Class kClass{class_v_.name_}; + static constexpr std::size_t kRank = kRank_; + using SpanType = SpanType_; -#ifdef DRY_RUN - return Fake(); -#else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); -#endif // DRY_RUN + using RawValT = std::conditional_t, + std::decay_t, SpanType>; + + using JniT_ = JniT; + + using Base = + ArrayRef>; + using Base::Base; + + using RefTag = std::conditional_t<(kRank_ > 1), jobject, SpanType>; + + // RefTag ctor (supports multi-dimensions, `jobject` if rank > 1). + LocalArray(std::size_t size, RefTag arr) + : Base(AdoptLocal{}, JniArrayHelper::NewArray( + size, + ClassRef_t::GetAndMaybeLoadClassRef( + static_cast(arr)), + arr)) {} + + template + LocalArray(ArrayViewHelper array_view_helper) + : LocalArray(AdoptLocal{}, array_view_helper.val_) {} + + // Rvalue ctor. + LocalArray(LocalArray&& rhs) + : Base(AdoptLocal{}, rhs.Release()) {} + + // Rvalue ctor. + template + LocalArray( + LocalArray&& rhs) + : Base(AdoptLocal{}, rhs.Release()) { + static_assert(std::is_same_v && kRank == kRank_ && + class_v == class_v_ && class_loader_v == class_loader_v_); } + + // Construct from decorated object lvalue (object is used as template). + // + // e.g. + // LocalArray arr { 5, LocalObject {args...} }; + // LocalArray arr { 5, GlobalObject {args...} }; + template < + template class ObjectContainer, + const auto& class_v, const auto& class_loader_v, const auto& jvm_v> + LocalArray( + std::size_t size, + const ObjectContainer& local_object) + : Base(JniArrayHelper::NewArray( + size, + ClassRef_t::GetAndMaybeLoadClassRef( + static_cast(local_object)), + static_cast(local_object))) {} + + operator jobject() { return static_cast(Base::object_ref_); } }; -template -struct InvokeHelper, kRank, true> { - template - static jcharArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +template < + template class ObjectContainer, + const auto& class_v_, const auto& class_loader_v_, const auto& jvm_v_> +LocalArray(std::size_t, + const ObjectContainer&) + -> LocalArray; -#ifdef DRY_RUN - return Fake(); -#else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); -#endif // DRY_RUN - } +template +LocalArray( + std::size_t, + const Scoped, jobject>&) + -> LocalArray; + +template +LocalArray(std::size_t, SpanType) + -> LocalArray; + +template +LocalArray(std::size_t, LocalArray) + -> LocalArray; + +template +LocalArray(std::size_t, + LocalArray) + -> LocalArray; + +template +LocalArray(std::size_t, LocalArray&&) + -> LocalArray; + +template +struct ProxyHelper; + +} // namespace jni + +// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" + +namespace jni { + +template +class GlobalClassLoader + : public ClassLoaderRef { + public: + using Base = ClassLoaderRef; + using Base::Base; + using SpanType = jobject; + + template + GlobalClassLoader(GlobalClassLoader&& rhs) + : Base(rhs.Release()) {} }; -template -struct InvokeHelper, kRank, true> { - template - static jshortArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +} // namespace jni + +namespace jni { + +static constexpr Class kJavaLangThrowable{ + "java/lang/Throwable", + Constructor{}, + Constructor{}, + Constructor{jstring{}, Self{}}, + Constructor{jstring{}, Self{}, jboolean{}, jboolean{}}, + Constructor{Self{}}}; + +} // namespace jni + +#include +#include + +namespace jni { + +//////////////////////////////////////////////////////////////////////////////// +// Rank 0: Static primitive types (e.g. int). +//////////////////////////////////////////////////////////////////////////////// +template <> +struct FieldHelper { + static inline jboolean GetValue(const jclass clazz, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticBooleanField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticBooleanField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper, kRank, true> { - template - static jintArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jboolean&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticBooleanField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast(jni::JniEnv::GetEnv()->CallStaticObjectMethod( - clazz, method_id, std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticBooleanField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper, kRank, true> { - template - static jfloatArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jbyte GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticByteField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticByteField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper, kRank, true> { - template - static jdoubleArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jbyte&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticByteField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->SetStaticByteField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper, kRank, true> { - template - static jlongArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jchar GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticCharField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticCharField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper, kRank, true> { - // Arrays of arrays (which this invoke represents) return object arrays - // (arrays themselves are objects, ergo object arrays). - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jchar&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticCharField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticCharField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jshort GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticShortField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticShortField(clazz, field_ref_); #endif // DRY_RUN } -}; -//////////////////////////////////////////////////////////////////////////////// -// Rank 2+ types, i.e. multi-dimension arrays (e.g. int[][], int[][][]). -//////////////////////////////////////////////////////////////////////////////// -template -struct InvokeHelper 1), jboolean>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jboolean), Rank >1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jshort&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticShortField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticShortField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper 1), jbyte>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jbyte), Rank >1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jint GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticIntField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticIntField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper 1), jchar>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jchar), Rank >1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jint&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticIntField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticIntField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper 1), jshort>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jshort), Rank >1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jlong GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticLongField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticLongField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper 1), jint>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jint), Rank >1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jlong&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticLongField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticLongField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper 1), jfloat>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jfloat), Rank >1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jfloat GetValue(const jclass clazz, const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticFloatField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return 123.f; #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticFloatField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper 1), jdouble>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jdouble), Rank >1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jfloat&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticFloatField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticFloatField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper 1), jlong>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jlong), Rank >1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jdouble GetValue(const jclass clazz, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticDoubleField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return 123.; #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticDoubleField(clazz, field_ref_); #endif // DRY_RUN } -}; -template -struct InvokeHelper 1), jarray>, kRank, true> { - // Arrays of arrays (which this invoke represents) return object arrays - // (arrays themselves are objects, ergo object arrays). - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jarray), Rank >1")), - clazz, method_id, ts...); + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jdouble&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticDoubleField")), clazz, + field_ref_, value); #ifdef DRY_RUN - return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + jni::JniEnv::GetEnv()->SetStaticDoubleField(clazz, field_ref_, value); #endif // DRY_RUN } }; -template -struct InvokeHelper 1), jobject>, kRank, true> { - template - static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, - Ts&&... ts) { - Trace(metaprogramming::LambdaToStr( - STR("CallStaticObjectMethod (jobject), Rank >1")), - clazz, method_id, ts...); +template <> +struct FieldHelper { + static inline jobject GetValue(const jclass clazz, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField")), clazz, + field_ref_); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, - std::forward(ts)...)); + return jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_); #endif // DRY_RUN } -}; - -} // namespace jni - -// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" - -namespace jni { - -// Designed to be the very last JniBind object to execute on the thread. -// Objects passed by move for lambdas will be destructed after any contents -// statements within their lambda, and `ThreadGuard` can't be moved into the -// lambda because its construction will be on the host thread. This static -// teardown guarantees a delayed destruction beyond any GlobalObject. -class ThreadLocalGuardDestructor { - public: - bool detach_thread_when_all_guards_released_ = false; - // By calling this the compiler is obligated to perform initalisation. - void ForceDestructionOnThreadClose() {} + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jobject&& new_value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField")), clazz, + field_ref_, new_value); - ~ThreadLocalGuardDestructor() { - if (detach_thread_when_all_guards_released_) { - JavaVM* jvm = JvmRefBase::GetJavaVm(); - if (jvm) { - jvm->DetachCurrentThread(); - } - } +#ifdef DRY_RUN +#else + jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, new_value); +#endif // DRY_RUN } }; -// ThreadGuard attaches and detaches JNIEnv* objects on the creation of new -// threads. All new threads which want to use JNI Wrapper must hold a -// ThreadGuard beyond the scope of all created objects. If the ThreadGuard -// needs to create an Env, it will also detach itself. -class ThreadGuard { - public: - ~ThreadGuard() { thread_guard_count_--; } - - ThreadGuard(ThreadGuard&) = delete; - ThreadGuard(ThreadGuard&&) = delete; - - template - friend class JvmRef; - - // This constructor must *never* be called before a |JvmRef| has been - // constructed. It depends on static setup from |JvmRef|. - [[nodiscard]] ThreadGuard() { - thread_local_guard_destructor.ForceDestructionOnThreadClose(); - - // Nested ThreadGuards should be permitted in the same way mutex locks are. - thread_guard_count_++; - if (thread_guard_count_ != 1) { - // SetEnv has been called prior, GetEnv is currently valid. - return; - } - - // Declarations for AttachCurrentThread are inconsistent across different - // JNI headers. This forces a cast to whatever the expected type is. - JavaVM* const vm = JvmRefBase::GetJavaVm(); - JNIEnv* jni_env = 0; - - using TypeForGetEnv = - metaprogramming::FunctionTraitsArg_t; - const int code = - vm->GetEnv(reinterpret_cast(&jni_env), JNI_VERSION_1_6); +template <> +struct FieldHelper { + static inline jstring GetValue(const jclass clazz, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField")), clazz, + field_ref_); - if (code != JNI_OK) { - using TypeForAttachment = metaprogramming::FunctionTraitsArg_t< - decltype(&JavaVM::AttachCurrentThread), 1>; - vm->AttachCurrentThread(reinterpret_cast(&jni_env), - nullptr); - thread_local_guard_destructor.detach_thread_when_all_guards_released_ = - true; - } - // Why not store this locally to ThreadGuard? - // - // JNIEnv is thread local static, and the context an object is built from - // may not have easy access to a JNIEnv* (or this ThreadGuard). For most - // constructions of new objects, the env is likely trivial (it's passed as - // part of the JNI call), however, if an object reference is moved from one - // thread to another, the JNIEnv* is certainly not available. - JniEnv::SetEnv(jni_env); +#ifdef DRY_RUN + return Fake(); +#else + return reinterpret_cast( + jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_)); +#endif // DRY_RUN } - private: - static inline thread_local int thread_guard_count_ = 0; - static inline thread_local ThreadLocalGuardDestructor - thread_local_guard_destructor{}; -}; - -} // namespace jni - -// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" - -namespace jni { - -using LocalStringImpl = - Scoped, jobject, - jstring>; - -// Represents and possibly builds a runtime Java String object. -// -// In order to use a string in memory (as opposed to only using it for function -// arguments), "Pin" the string. -// -// Like |jobjects|, |jstring|s can be either local or global with the same -// ownership semantics. -class LocalString : public LocalStringImpl { - public: - using Base = LocalStringImpl; - using Base::Base; - - LocalString(LocalObject&& obj) - : Base(AdoptLocal{}, static_cast(obj.Release())) {} - - template - LocalString(ArrayViewHelper array_view_helper) - : LocalString(AdoptLocal{}, array_view_helper.val_) {} + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jstring&& new_value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField")), clazz, + field_ref_, new_value); - // Returns a StringView which possibly performs an expensive pinning - // operation. String objects can be pinned multiple times. - UtfStringView Pin() { return {RefBase::object_ref_}; } +#ifdef DRY_RUN +#else + jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, new_value); +#endif // DRY_RUN + } }; -} // namespace jni - -// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" - -#include -#include - -namespace jni { - -// Represents a an array object (e.g. int[], float[][], Object[], etc). -// Currently GlobalArrays do not exist, as reasoning about the lifecycles of the -// underlying objects is non-trivial, e.g. a GlobalArray taking a local object -// would result in a possibly unexpected extension of lifetime. -template -class LocalArray - : public ArrayRef< - JniT> { - public: - static constexpr Class kClass{class_v_.name_}; - static constexpr std::size_t kRank = kRank_; - using SpanType = SpanType_; - - using RawValT = std::conditional_t, - std::decay_t, SpanType>; - - using JniT_ = JniT; - - using Base = - ArrayRef>; - using Base::Base; - - using RefTag = std::conditional_t<(kRank_ > 1), jobject, SpanType>; - - // RefTag ctor (supports multi-dimensions, `jobject` if rank > 1). - LocalArray(std::size_t size, RefTag arr) - : Base(AdoptLocal{}, JniArrayHelper::NewArray( - size, - ClassRef_t::GetAndMaybeLoadClassRef( - static_cast(arr)), - arr)) {} - - template - LocalArray(ArrayViewHelper array_view_helper) - : LocalArray(AdoptLocal{}, array_view_helper.val_) {} - - // Rvalue ctor. - LocalArray(LocalArray&& rhs) - : Base(AdoptLocal{}, rhs.Release()) {} +//////////////////////////////////////////////////////////////////////////////// +// Rank 1: Static single dimension arrays (e.g. int[]). +//////////////////////////////////////////////////////////////////////////////// +template +struct StaticBaseFieldArrayHelper { + static inline ArrayType GetValue(const jobject object_ref, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetObjectField")), object_ref, + field_ref_); - // Rvalue ctor. - template - LocalArray( - LocalArray&& rhs) - : Base(AdoptLocal{}, rhs.Release()) { - static_assert(std::is_same_v && kRank == kRank_ && - class_v == class_v_ && class_loader_v == class_loader_v_); +#ifdef DRY_RUN + return Fake(); +#else + return static_cast( + jni::JniEnv::GetEnv()->GetObjectField(object_ref, field_ref_)); +#endif // DRY_RUN } - // Construct from decorated object lvalue (object is used as template). - // - // e.g. - // LocalArray arr { 5, LocalObject {args...} }; - // LocalArray arr { 5, GlobalObject {args...} }; - template < - template class ObjectContainer, - const auto& class_v, const auto& class_loader_v, const auto& jvm_v> - LocalArray( - std::size_t size, - const ObjectContainer& local_object) - : Base(JniArrayHelper::NewArray( - size, - ClassRef_t::GetAndMaybeLoadClassRef( - static_cast(local_object)), - static_cast(local_object))) {} + static inline void SetValue(const jobject object_ref, + const jfieldID field_ref_, ArrayType&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetObjectField")), object_ref, + field_ref_, value); - operator jobject() { return static_cast(Base::object_ref_); } +#ifdef DRY_RUN +#else + jni::JniEnv::GetEnv()->SetObjectField(object_ref, field_ref_, value); +#endif // DRY_RUN + } }; -template < - template class ObjectContainer, - const auto& class_v_, const auto& class_loader_v_, const auto& jvm_v_> -LocalArray(std::size_t, - const ObjectContainer&) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -LocalArray( - std::size_t, - const Scoped, jobject>&) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -LocalArray(std::size_t, SpanType) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -LocalArray(std::size_t, LocalArray) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -LocalArray(std::size_t, - LocalArray) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -LocalArray(std::size_t, LocalArray&&) - -> LocalArray; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -template -struct ProxyHelper; +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -} // namespace jni +template +struct FieldHelper, kRank, true, void> + : StaticBaseFieldArrayHelper {}; -// IWYU pragma: private, include "third_party/jni_wrapper/jni_bind.h" +//////////////////////////////////////////////////////////////////////////////// +// Rank 1: Static jobjects. +// Rank 2+: Static multi-dimension arrays (e.g. int[][], int[][][]). +//////////////////////////////////////////////////////////////////////////////// +template +struct FieldHelper< + T, kRank, true, + std::enable_if_t<(std::is_same_v || (kRank > 1))>> { + static inline jobjectArray GetValue(const jclass clazz, + const jfieldID field_ref_) { + Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField, Rank 1+")), + clazz, field_ref_); -namespace jni { +#ifdef DRY_RUN + return Fake(); +#else + return static_cast( + jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_)); +#endif // DRY_RUN + } -template -class GlobalClassLoader - : public ClassLoaderRef { - public: - using Base = ClassLoaderRef; - using Base::Base; - using SpanType = jobject; + static inline void SetValue(const jclass clazz, const jfieldID field_ref_, + jobjectArray&& value) { + Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField, Rank 1+")), + clazz, field_ref_, value); - template - GlobalClassLoader(GlobalClassLoader&& rhs) - : Base(rhs.Release()) {} +#ifdef DRY_RUN +#else + jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, value); +#endif // DRY_RUN + } }; } // namespace jni -namespace jni { - -static constexpr Class kJavaLangThrowable{ - "java/lang/Throwable", - Constructor{}, - Constructor{}, - Constructor{jstring{}, Self{}}, - Constructor{jstring{}, Self{}, jboolean{}, jboolean{}}, - Constructor{Self{}}}; - -} // namespace jni - #include #include +#include namespace jni { //////////////////////////////////////////////////////////////////////////////// -// Rank 0: Static primitive types (e.g. int). +// Rank 0 type (aka void). +//////////////////////////////////////////////////////////////////////////////// +template <> +struct InvokeHelper { + template + static void Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticVoidMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN +#else + jni::JniEnv::GetEnv()->CallStaticVoidMethod(clazz, method_id, + std::forward(ts)...); +#endif // DRY_RUN + } +}; + //////////////////////////////////////////////////////////////////////////////// +// Rank 0 types, i.e. the primitive type itself (e.g. int). +//////////////////////////////////////////////////////////////////////////////// +template <> +struct InvokeHelper { + template + static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticBooleanMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN + return Fake(); +#else + return jni::JniEnv::GetEnv()->CallStaticBooleanMethod( + clazz, method_id, std::forward(ts)...); +#endif // DRY_RUN + } +}; + +template <> +struct InvokeHelper { + template + static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticByteMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN + return Fake(); +#else + return jni::JniEnv::GetEnv()->CallStaticByteMethod(clazz, method_id, + std::forward(ts)...); +#endif // DRY_RUN + } +}; + +template <> +struct InvokeHelper { + template + static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticCharMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN + return Fake(); +#else + return jni::JniEnv::GetEnv()->CallStaticCharMethod(clazz, method_id, + std::forward(ts)...); +#endif // DRY_RUN + } +}; + +template <> +struct InvokeHelper { + template + static jboolean Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticShortMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN + return Fake(); +#else + return jni::JniEnv::GetEnv()->CallStaticShortMethod( + clazz, method_id, std::forward(ts)...); +#endif // DRY_RUN + } +}; + +template <> +struct InvokeHelper { + template + static jint Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticIntMethod")), clazz, + method_id, ts...); + +#ifdef DRY_RUN + return Fake(); +#else + return jni::JniEnv::GetEnv()->CallStaticIntMethod(clazz, method_id, + std::forward(ts)...); +#endif // DRY_RUN + } +}; + template <> -struct FieldHelper { - static inline jboolean GetValue(const jclass clazz, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticBooleanField")), clazz, - field_ref_); +struct InvokeHelper { + template + static jlong Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticLongMethod")), clazz, + method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticBooleanField(clazz, field_ref_); + return jni::JniEnv::GetEnv()->CallStaticLongMethod(clazz, method_id, + std::forward(ts)...); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jboolean&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticBooleanField")), clazz, - field_ref_, value); +template <> +struct InvokeHelper { + template + static jfloat Invoke(jobject, jclass clazz, jmethodID method_id, Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticFloatMethod")), clazz, + method_id, ts...); #ifdef DRY_RUN + return 123.f; #else - jni::JniEnv::GetEnv()->SetStaticBooleanField(clazz, field_ref_, value); + return jni::JniEnv::GetEnv()->CallStaticFloatMethod( + clazz, method_id, std::forward(ts)...); #endif // DRY_RUN } }; template <> -struct FieldHelper { - static inline jbyte GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticByteField")), clazz, - field_ref_); +struct InvokeHelper { + template + static jdouble Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticDoubleMethod")), clazz, + method_id, ts...); #ifdef DRY_RUN - return Fake(); + return 123.; #else - return jni::JniEnv::GetEnv()->GetStaticByteField(clazz, field_ref_); + return jni::JniEnv::GetEnv()->CallStaticDoubleMethod( + clazz, method_id, std::forward(ts)...); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jbyte&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticByteField")), clazz, - field_ref_, value); +template <> +struct InvokeHelper { + // This always returns a local reference which should be embedded in type + // information wherever this is used. + template + static jobject Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod")), clazz, + method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - return jni::JniEnv::GetEnv()->SetStaticByteField(clazz, field_ref_, value); + return jni::JniEnv::GetEnv()->CallStaticObjectMethod( + clazz, method_id, std::forward(ts)...); #endif // DRY_RUN } }; template <> -struct FieldHelper { - static inline jchar GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticCharField")), clazz, - field_ref_); +struct InvokeHelper { + template + static jobject Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod")), clazz, + method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticCharField(clazz, field_ref_); + return jni::JniEnv::GetEnv()->CallStaticObjectMethod( + clazz, method_id, std::forward(ts)...); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jchar&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticCharField")), clazz, - field_ref_, value); +//////////////////////////////////////////////////////////////////////////////// +// Rank 1 types, i.e. single dimension arrays (e.g. int[]). +//////////////////////////////////////////////////////////////////////////////// +template +struct InvokeHelper, kRank, true> { + template + static jbooleanArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticCharField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jshort GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticShortField")), clazz, - field_ref_); +template +struct InvokeHelper, kRank, true> { + template + static jbyteArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticShortField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jshort&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticShortField")), clazz, - field_ref_, value); +template +struct InvokeHelper, kRank, true> { + template + static jcharArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticShortField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jint GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticIntField")), clazz, - field_ref_); +template +struct InvokeHelper, kRank, true> { + template + static jshortArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticIntField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jint&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticIntField")), clazz, - field_ref_, value); +template +struct InvokeHelper, kRank, true> { + template + static jintArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticIntField(clazz, field_ref_, value); + return static_cast(jni::JniEnv::GetEnv()->CallStaticObjectMethod( + clazz, method_id, std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jlong GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticLongField")), clazz, - field_ref_); +template +struct InvokeHelper, kRank, true> { + template + static jfloatArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticLongField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jlong&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticLongField")), clazz, - field_ref_, value); +template +struct InvokeHelper, kRank, true> { + template + static jdoubleArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticLongField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jfloat GetValue(const jclass clazz, const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticFloatField")), clazz, - field_ref_); +template +struct InvokeHelper, kRank, true> { + template + static jlongArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return 123.f; + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticFloatField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jfloat&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticFloatField")), clazz, - field_ref_, value); +template +struct InvokeHelper, kRank, true> { + // Arrays of arrays (which this invoke represents) return object arrays + // (arrays themselves are objects, ergo object arrays). + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticFloatField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jdouble GetValue(const jclass clazz, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticDoubleField")), clazz, - field_ref_); +template +struct InvokeHelper, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr(STR("CallStaticObjectMethod, Rank 1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return 123.; + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticDoubleField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jdouble&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticDoubleField")), clazz, - field_ref_, value); +//////////////////////////////////////////////////////////////////////////////// +// Rank 2+ types, i.e. multi-dimension arrays (e.g. int[][], int[][][]). +//////////////////////////////////////////////////////////////////////////////// +template +struct InvokeHelper 1), jboolean>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jboolean), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticDoubleField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jobject GetValue(const jclass clazz, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField")), clazz, - field_ref_); +template +struct InvokeHelper 1), jbyte>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jbyte), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jobject&& new_value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField")), clazz, - field_ref_, new_value); +template +struct InvokeHelper 1), jchar>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jchar), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, new_value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -template <> -struct FieldHelper { - static inline jstring GetValue(const jclass clazz, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField")), clazz, - field_ref_); +template +struct InvokeHelper 1), jshort>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jshort), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return reinterpret_cast( - jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_)); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jstring&& new_value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField")), clazz, - field_ref_, new_value); +template +struct InvokeHelper 1), jint>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jint), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, new_value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; -//////////////////////////////////////////////////////////////////////////////// -// Rank 1: Static single dimension arrays (e.g. int[]). -//////////////////////////////////////////////////////////////////////////////// -template -struct StaticBaseFieldArrayHelper { - static inline ArrayType GetValue(const jobject object_ref, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetObjectField")), object_ref, - field_ref_); +template +struct InvokeHelper 1), jfloat>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jfloat), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN - return Fake(); + return Fake(); #else - return static_cast( - jni::JniEnv::GetEnv()->GetObjectField(object_ref, field_ref_)); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jobject object_ref, - const jfieldID field_ref_, ArrayType&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetObjectField")), object_ref, - field_ref_, value); +template +struct InvokeHelper 1), jdouble>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jdouble), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetObjectField(object_ref, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; +struct InvokeHelper 1), jlong>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jlong), Rank >1")), + clazz, method_id, ts...); -template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; +#ifdef DRY_RUN + return Fake(); +#else + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); +#endif // DRY_RUN + } +}; template -struct FieldHelper, kRank, true, void> - : StaticBaseFieldArrayHelper {}; - -//////////////////////////////////////////////////////////////////////////////// -// Rank 1: Static jobjects. -// Rank 2+: Static multi-dimension arrays (e.g. int[][], int[][][]). -//////////////////////////////////////////////////////////////////////////////// -template -struct FieldHelper< - T, kRank, true, - std::enable_if_t<(std::is_same_v || (kRank > 1))>> { - static inline jobjectArray GetValue(const jclass clazz, - const jfieldID field_ref_) { - Trace(metaprogramming::LambdaToStr(STR("GetStaticObjectField, Rank 1+")), - clazz, field_ref_); +struct InvokeHelper 1), jarray>, kRank, true> { + // Arrays of arrays (which this invoke represents) return object arrays + // (arrays themselves are objects, ergo object arrays). + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jarray), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN return Fake(); #else return static_cast( - jni::JniEnv::GetEnv()->GetStaticObjectField(clazz, field_ref_)); + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } +}; - static inline void SetValue(const jclass clazz, const jfieldID field_ref_, - jobjectArray&& value) { - Trace(metaprogramming::LambdaToStr(STR("SetStaticObjectField, Rank 1+")), - clazz, field_ref_, value); +template +struct InvokeHelper 1), jobject>, kRank, true> { + template + static jobjectArray Invoke(jobject, jclass clazz, jmethodID method_id, + Ts&&... ts) { + Trace(metaprogramming::LambdaToStr( + STR("CallStaticObjectMethod (jobject), Rank >1")), + clazz, method_id, ts...); #ifdef DRY_RUN + return Fake(); #else - jni::JniEnv::GetEnv()->SetStaticObjectField(clazz, field_ref_, value); + return static_cast( + jni::JniEnv::GetEnv()->CallStaticObjectMethod(clazz, method_id, + std::forward(ts)...)); #endif // DRY_RUN } }; @@ -9739,13 +9987,23 @@ template ; + // C++17 augmentations. using MethodMapT = metaprogramming::InvocableMap; using FieldMapT = metaprogramming::QueryableMap_t; + + // C++ 20 augmentations. + using MethodMap20T = + metaprogramming::InvocableMap20_t; + using FieldMap20T = + metaprogramming::QueryableMap20_t; }; +// C++17 augmentations. template using StaticRefHelperMethodMap_t = @@ -9758,21 +10016,45 @@ using StaticRefHelperFieldMap_t = typename StaticRefHelper::FieldMapT; +// C++20 augmentations. +template +using StaticRefHelperMethodMap20_t = + typename StaticRefHelper::MethodMap20T; +template +using StaticRefHelperFieldMap20_t = + typename StaticRefHelper::FieldMap20T; + template struct StaticRef - : public StaticRefHelperMethodMap_t< - StaticRef, class_v_, - class_loader_v_, jvm_v_>, + : public + // C++17 augmentations. + StaticRefHelperMethodMap_t, + class_v_, class_loader_v_, jvm_v_>, StaticRefHelperFieldMap_t, - class_v_, class_loader_v_, jvm_v_> { + class_v_, class_loader_v_, jvm_v_>, + // C++ 20 augmentations. + StaticRefHelperMethodMap20_t, + class_v_, class_loader_v_, jvm_v_>, + StaticRefHelperFieldMap20_t, + class_v_, class_loader_v_, jvm_v_> { using JniT = JniT; jclass GetJClass() const { return ClassRef_t::GetAndMaybeLoadClassRef(nullptr); } + //////////////////////////////////////////////////////////////////////////////// + // Implementation: C++17 + clang + // Supports syntax like: "obj("foo", 123, args)", "obj["foo"].Get()" + // This syntax is less portable and may be removed in a major future release. + //////////////////////////////////////////////////////////////////////////////// +#if __clang__ template auto InvocableMapCall(const char* key, Args&&... args) const { using IdT = Id; @@ -9791,6 +10073,31 @@ struct StaticRef auto QueryableMapCall(const char* key) const { return FieldRef{GetJClass(), nullptr}; } +#endif // __clang__ + +#if __cplusplus >= 202002L + // Invoked through CRTP from InvocableMap, C++20 only. + template + auto InvocableMap20Call(Args&&... args) const { + using IdT = Id; + using MethodSelectionForArgs = + OverloadSelector; + + static_assert(MethodSelectionForArgs::kIsValidArgSet, + "JNI Error: Invalid argument set."); + + return MethodSelectionForArgs::OverloadRef::Invoke( + GetJClass(), nullptr, std::forward(args)...); + } + + // Invoked through CRTP from QueryableMap20, C++20 only. + template + auto QueryableMap20Call() const { + return FieldRef{GetJClass(), nullptr}; + } +#endif // __cplusplus >= 202002L }; } // namespace jni diff --git a/metaprogramming/BUILD b/metaprogramming/BUILD index 69114bec..7505ed81 100644 --- a/metaprogramming/BUILD +++ b/metaprogramming/BUILD @@ -605,6 +605,30 @@ cc_test( ], ) +################################################################################ +# Invocable Map 20. +################################################################################ +cc_library( + name = "invocable_map_20", + hdrs = ["invocable_map_20.h"], + deps = [ + ":modified_max", + ":string_literal", + ], +) + +cc_test( + name = "invocable_map_test_20", + srcs = ["invocable_map_20_test.cc"], + tags = ["cpp20"], + deps = [ + ":invocable_map_20", + ":modified_max", + ":string_literal", + "@googletest//:gtest_main", + ], +) + ################################################################################ # Invoke. ################################################################################ @@ -961,6 +985,35 @@ cc_test( ], ) +################################################################################ +# Queryable Map 20. +################################################################################ +cc_library( + name = "queryable_map_20", + hdrs = ["queryable_map_20.h"], + deps = [ + ":interleave", + ":lambda_string", + ":modified_max", + ":string_literal", + ":tuple_from_size", + ":tuple_manipulation", + ":type_of_nth_element", + ], +) + +cc_test( + name = "queryable_map_20_test", + srcs = ["queryable_map_20_test.cc"], + tags = ["cpp20"], + deps = [ + ":modified_max", + ":queryable_map_20", + ":string_literal", + "@googletest//:gtest_main", + ], +) + ################################################################################ # Reduce. ################################################################################ diff --git a/metaprogramming/invocable_map.h b/metaprogramming/invocable_map.h index e1c99afc..40badde6 100644 --- a/metaprogramming/invocable_map.h +++ b/metaprogramming/invocable_map.h @@ -26,7 +26,7 @@ namespace jni::metaprogramming { template + typename TupContainerT, const auto TupContainerT::* nameable_member> class InvocableMap; // This is an interface that can be inherited from to expose an operator(...). @@ -42,7 +42,7 @@ class InvocableMap; // auto InvocableMapCall(const char* key, Args&&... args); // // If i is the index where |tup_container_v.*nameable_member|.name_ == key, -// then InvocablemapCall will forward the args from operator() with the +// then InvocableMapCall will forward the args from operator() with the // same args. Static memory can be used in this function call and it will // be unique because of the I non-type template parameter. // @@ -54,23 +54,23 @@ class InvocableMap; // the the const char cannot be propagated without losing its constexpr-ness, // and so the clang extension can no longer restrict function candidates. template ::*nameable_member> + const auto std::decay_t::* nameable_member> using InvocableMap_t = InvocableMap, nameable_member>; template class InvocableMapEntry; template class InvocableMapBase {}; template class InvocableMapBase> @@ -78,11 +78,12 @@ class InvocableMapBase... { public: using InvocableMapEntry::operator()...; + nameable_member, idxs>:: + operator()...; }; template class InvocableMapEntry { public: @@ -116,7 +117,7 @@ class InvocableMapEntry { //============================================================================== template + typename TupContainerT, const auto TupContainerT::* nameable_member> class InvocableMap : public InvocableMapBase< CrtpBase, tup_container_v, TupContainerT, nameable_member, diff --git a/metaprogramming/invocable_map_20.h b/metaprogramming/invocable_map_20.h new file mode 100644 index 00000000..4162b87c --- /dev/null +++ b/metaprogramming/invocable_map_20.h @@ -0,0 +1,100 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +#ifndef JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H +#define JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H + +#include +#include +#include +#include +#include + +#include "modified_max.h" +#include "string_literal.h" + +namespace jni::metaprogramming { + +// This class enables compile time lookup that perfectly forward arguments +// to a method named `Call`. This is a C++ 20 version of InvocableMap. +// +// This is an interface that can be inherited from to expose a method named +// `Call`. It provides compile time string index lookup with no macros. +// +// To use this API, inherit from this class using template types as follows: +// +// |CrtpBase|: The name of the class inheriting from the map. This class +// will inherit an operator(). It must implement this exact signature: +// +// template +// auto InvocableMap20Call(Args&&... args); +// +// If i is the index where |tup_container_v.*nameable_member|.name_ == key, +// then InvocableMap20Call will forward the args from operator() with the +// same args. Static memory can be used in this function call and it will +// be unique because of the I non-type template parameter. +// +// |tup_container_v| is a static instance of an object whose |nameable_member| +// contains a public field called name_. It might seem strange not to +// directly pass a const auto&, however, this prevents accessing subobjects. +// +// The motivation for using inheritance as opposed to a simple member is that +// the the const char cannot be propagated without losing its constexpr-ness, +// and so the clang extension can no longer restrict function candidates. +template +class InvocableMap20 { +#if __cplusplus >= 202002L + public: + template + constexpr auto Do(Args&&... args) { + return (*static_cast(this)) + .template InvocableMap20Call( + std::forward(args)...); + } + + template + static constexpr std::size_t SelectCandidate(StringLiteral string_literal, + std::index_sequence) { + return ModifiedMax( + {((std::get(tup_container_v.*nameable_member).name_ == + std::string_view{string_literal.value}) + ? std::size_t{Is} + : kNegativeOne)..., + kNegativeOne}); + } + + template + constexpr auto Call(Args&&... args) { + return Do>>())>( + std::forward(args)...); + } +#endif // __cplusplus >= 202002L +}; + +template ::* nameable_member> +using InvocableMap20_t = + InvocableMap20, nameable_member>; + +} // namespace jni::metaprogramming + +#endif // JNI_BIND_METAPROGRAMMING_INVOCABLE_MAP_20_H diff --git a/metaprogramming/invocable_map_20_test.cc b/metaprogramming/invocable_map_20_test.cc new file mode 100644 index 00000000..c8eb7856 --- /dev/null +++ b/metaprogramming/invocable_map_20_test.cc @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +#include "invocable_map_20.h" + +#include +#include +#include +#include + +#include "string_literal.h" +#include + +using jni::metaprogramming::InvocableMap20_t; +using jni::metaprogramming::StringLiteral; + +struct Str { + const char* name_; + constexpr Str(const char* name) : name_(name) {} +}; + +struct NameContainer { + std::tuple container1_; + std::tuple container2_; +}; + +constexpr NameContainer name_container{ + {{"Foo"}, {"Bar"}, {"Baz"}}, + {{"Fizz"}, {"Buzz"}, {"Bang"}}, +}; + +//////////////////////////////////////////////////////////////////////////////// +class SampleClassNowExposingCallOperator1 + : public InvocableMap20_t { + protected: + friend InvocableMap20_t; + + template + auto InvocableMap20Call(Args&&... ts) { + if (std::string_view(key_literal.value) == "Foo") { + EXPECT_TRUE(I == 0); + EXPECT_TRUE((std::is_same_v, std::tuple>)); + } else if (std::string_view(key_literal.value) == "Bar") { + EXPECT_TRUE(I == 1); + EXPECT_TRUE( + (std::is_same_v, std::tuple>)); + } else if (std::string_view(key_literal.value) == "Baz") { + EXPECT_TRUE(I == 2); + EXPECT_TRUE(( + std::is_same_v, std::tuple>)); + } else { + FAIL(); + } + } +}; + +TEST(InvocableMapTest1, HasCorrectTypesAndForwardsCalls) { + SampleClassNowExposingCallOperator1 val; + val.Call<"Foo">(1); + val.Call<"Bar">(2.f, 3.f); + val.Call<"Baz">(4, 5.f, double{6}); + + // By design, doesn't compile. + // val("BazNar", 7, 8, 9); +} diff --git a/metaprogramming/queryable_map_20.h b/metaprogramming/queryable_map_20.h new file mode 100644 index 00000000..ddf16743 --- /dev/null +++ b/metaprogramming/queryable_map_20.h @@ -0,0 +1,92 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +#ifndef JNI_BIND_METAPROGRAMMING_QUERYABLE_MAP_20_H +#define JNI_BIND_METAPROGRAMMING_QUERYABLE_MAP_20_H + +#include +#include +#include +#include +#include + +#include "modified_max.h" +#include "string_literal.h" + +namespace jni::metaprogramming { + +// This is an interface that can be inherited from to expose an +// operator.Access<"name">(). It provides compile time string index lookup with +// no macros although it is dependent on a clang extension. +// +// To use this API, inherit from this class using template types as follows: +// +// |CrtpBase|: The name of the class inheriting from the map. This class +// will inherit an operator[]. It must implement this exact signature: +// +// template +// auto QueryableMap20Call(); +// +// |tup_container_v| is a static instance of an object whose |nameable_member| +// contains a public field called name_. It might seem strange not to +// directly pass a const auto&, however, this prevents accessing subobjects. +// +// The motivation for using inheritance as opposed to a simple member is that +// the the const char cannot be propagated without losing its constexpr-ness, +// and so the clang extension can no longer restrict function candidates. +template +class QueryableMap20 { +#if __cplusplus >= 202002L + private: + template + constexpr auto Do() { + return (*static_cast(this)) + .template QueryableMap20Call(); + } + + template + static constexpr std::size_t SelectCandidate(StringLiteral string_literal, + std::index_sequence) { + return ModifiedMax( + {((std::get(tup_container_v.*nameable_member).name_ == + std::string_view{string_literal.value}) + ? std::size_t{Is} + : kNegativeOne)..., + kNegativeOne}); + } + + public: + template + constexpr auto Access() { + return Do>>())>(); + } +#endif // __cplusplus >= 202002L +}; + +template ::* nameable_member> +using QueryableMap20_t = + QueryableMap20, nameable_member>; + +} // namespace jni::metaprogramming + +#endif // JNI_BIND_METAPROGRAMMING_QUERYABLE_MAP_20_H diff --git a/metaprogramming/queryable_map_20_test.cc b/metaprogramming/queryable_map_20_test.cc new file mode 100644 index 00000000..8a06e5b4 --- /dev/null +++ b/metaprogramming/queryable_map_20_test.cc @@ -0,0 +1,79 @@ +/* + * Copyright 2024 Google LLC + * + * 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. + */ + +#include "queryable_map_20.h" + +#include +#include +#include +#include + +#include "string_literal.h" +#include + +#if __cplusplus >= 202002L + +using jni::metaprogramming::QueryableMap20_t; +using jni::metaprogramming::StringLiteral; + +struct Str { + const char* name_; + constexpr Str(const char* name) : name_(name) {} +}; + +struct NameContainer { + std::tuple container1_; + std::tuple container2_; +}; + +constexpr NameContainer name_container{ + {{"Foo"}, {"Bar"}, {"Baz"}}, + {{"Fizz"}, {"Buzz"}, {"Bang"}}, +}; + +//////////////////////////////////////////////////////////////////////////////// +class SampleClassNowExposingCallOperator1 + : public QueryableMap20_t { + protected: + friend QueryableMap20_t; + + template + auto QueryableMap20Call() { + if (std::string_view(key_literal.value) == "Foo") { + EXPECT_TRUE(I == 0); + } else if (std::string_view(key_literal.value) == "Bar") { + EXPECT_TRUE(I == 1); + } else if (std::string_view(key_literal.value) == "Baz") { + EXPECT_TRUE(I == 2); + } else { + FAIL(); + } + } +}; + +TEST(QueryableMap20Test1, HasCorrectTypesAndForwardsCalls) { + SampleClassNowExposingCallOperator1 val; + val.Access<"Foo">(); + val.Access<"Bar">(); + val.Access<"Baz">(); + + // By design, doesn't compile. + // val("BazNar", 7, 8, 9); +} + +#endif // __cplusplus >= 202002L diff --git a/metaprogramming/string_literal.h b/metaprogramming/string_literal.h index e93a4b85..b6b88ab1 100644 --- a/metaprogramming/string_literal.h +++ b/metaprogramming/string_literal.h @@ -58,9 +58,9 @@ struct StringLiteral { template StringLiteral(const char (&str)[N]) -> StringLiteral; +} // namespace jni::metaprogramming + #endif // __cplusplus >= 202002L #endif // __cplusplus -} // namespace jni::metaprogramming - #endif // JNI_BIND_METAPROGRAMMING_STRING_LITERAL_H_