// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef JNI_ZERO_DEFAULT_CONVERSIONS_H_ #define JNI_ZERO_DEFAULT_CONVERSIONS_H_ #include #include #include #include "third_party/jni_zero/common_apis.h" #include "third_party/jni_zero/jni_zero.h" namespace jni_zero { namespace internal { template concept IsJavaRef = std::is_base_of_v, T>; template concept HasReserve = requires(T t) { t.reserve(0); }; template concept HasPushBack = requires(T t, T::value_type v) { t.push_back(v); }; template concept HasInsert = requires(T t, T::value_type v) { t.insert(v); }; template concept IsMap = requires(T t) { typename T::key_type; typename T::mapped_type; }; template concept IsContainer = requires(T t) { !IsMap; typename T::value_type; t.begin(); t.end(); t.size(); }; template concept IsObjectContainer = IsContainer && !std::is_arithmetic_v; template concept IsOptional = std::same_as>; } // namespace internal // Allow conversions using std::optional by wrapping non-optional conversions. template inline T FromJniType(JNIEnv* env, const JavaRef& j_object) { if (!j_object) { return std::nullopt; } return FromJniType(env, j_object); } template inline ScopedJavaLocalRef ToJniType(JNIEnv* env, const T& opt_value) { if (!opt_value) { return nullptr; } return ToJniType(env, opt_value.value()); } // Convert Java array -> container type using FromJniType() on each element. template inline ContainerType FromJniArray(JNIEnv* env, const JavaRef& j_object) { jobjectArray j_array = static_cast(j_object.obj()); using ElementType = std::remove_const_t; constexpr bool has_push_back = internal::HasPushBack; constexpr bool has_insert = internal::HasInsert; static_assert(has_push_back || has_insert, "Template type not supported."); jsize array_jsize = env->GetArrayLength(j_array); ContainerType ret; if constexpr (internal::HasReserve) { size_t array_size = static_cast(array_jsize); ret.reserve(array_size); } for (jsize i = 0; i < array_jsize; ++i) { jobject j_element = env->GetObjectArrayElement(j_array, i); // Do not call FromJni for jobject->jobject. if constexpr (std::is_base_of_v, ElementType>) { if constexpr (has_push_back) { ret.emplace_back(env, j_element); } else if constexpr (has_insert) { ret.emplace(env, j_element); } } else { auto element = ScopedJavaLocalRef::Adopt(env, j_element); if constexpr (has_push_back) { ret.push_back(FromJniType(env, element)); } else if constexpr (has_insert) { ret.insert(FromJniType(env, element)); } } } return ret; } // Convert container type -> Java array using ToJniType() on each element. template inline ScopedJavaLocalRef ToJniArray(JNIEnv* env, const ContainerType& collection, jclass clazz) { using ElementType = std::remove_const_t; size_t array_size = collection.size(); jsize array_jsize = static_cast(array_size); jobjectArray j_array = env->NewObjectArray(array_jsize, clazz, nullptr); CheckException(env); jsize i = 0; for (auto& value : collection) { // Do not call ToJni for jobject->jobject. if constexpr (std::is_base_of_v, ElementType>) { env->SetObjectArrayElement(j_array, i, value.obj()); } else { ScopedJavaLocalRef element = ToJniType(env, value); env->SetObjectArrayElement(j_array, i, element.obj()); } ++i; } return ScopedJavaLocalRef(env, j_array); } #define DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(T) \ template <> \ JNI_ZERO_COMPONENT_BUILD_EXPORT std::vector FromJniArray>( \ JNIEnv * env, const JavaRef& j_object); \ template <> \ JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef \ ToJniArray>(JNIEnv * env, const std::vector& vec); DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int64_t) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int32_t) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int16_t) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(uint16_t) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(uint8_t) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(bool) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(float) DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(double) #undef DECLARE_PRIMITIVE_ARRAY_CONVERSIONS // Specialization for ByteArrayView. template <> inline ByteArrayView FromJniArray( JNIEnv* env, const JavaRef& j_object) { jbyteArray j_array = static_cast(j_object.obj()); return ByteArrayView(env, j_array); } // There is a circular dependency between common_apis.cc and here. JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef CollectionToArray(JNIEnv* env, const JavaRef& collection); JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef ArrayToList( JNIEnv* env, const JavaRef& array); template inline ContainerType FromJniCollection(JNIEnv* env, const JavaRef& j_collection) { ScopedJavaLocalRef arr = CollectionToArray(env, j_collection); return FromJniArray(env, arr); } // Convert container type -> Java array using ToJniType() on each element. template inline ScopedJavaLocalRef ToJniList(JNIEnv* env, const ContainerType& collection) { ScopedJavaLocalRef arr = ToJniArray(env, collection, g_object_class); return ArrayToList(env, arr); } JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef MapToArray( JNIEnv* env, const JavaRef& map); JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef ArrayToMap( JNIEnv* env, const JavaRef& array); // Convert Map -> stl map type using FromJniType() on each key & value. template inline ContainerType FromJniType(JNIEnv* env, const JavaRef& j_object) { using KeyType = ContainerType::key_type; using ValueType = ContainerType::mapped_type; ScopedJavaLocalRef j_array = MapToArray(env, j_object); jsize array_jsize = env->GetArrayLength(j_array.obj()); ContainerType ret; if constexpr (internal::HasReserve) { size_t array_size = static_cast(array_jsize); ret.reserve(array_size / 2); } for (jsize i = 0; i < array_jsize; i += 2) { // No need to call CheckException() since we know the array is of the // correct size, since we are the ones who created it. jobject j_key = env->GetObjectArrayElement(j_array.obj(), i); jobject j_value = env->GetObjectArrayElement(j_array.obj(), i + 1); // Do not call FromJni for jobject->jobject. if constexpr (internal::IsJavaRef && internal::IsJavaRef) { ret.emplace(std::piecewise_construct, std::forward_as_tuple(env, j_key), std::forward_as_tuple(env, j_value)); } else if constexpr (internal::IsJavaRef) { auto value = ScopedJavaLocalRef::Adopt(env, j_value); ret.emplace(std::piecewise_construct, std::forward_as_tuple(env, j_key), FromJniType(env, value)); } else if constexpr (internal::IsJavaRef) { auto key = ScopedJavaLocalRef::Adopt(env, j_key); ret.emplace(std::piecewise_construct, FromJniType(env, key), std::forward_as_tuple(env, j_value)); } else { auto key = ScopedJavaLocalRef::Adopt(env, j_key); auto value = ScopedJavaLocalRef::Adopt(env, j_value); ret.emplace(FromJniType(env, key), FromJniType(env, value)); } } return ret; } // Convert stl map -> Map type using ToJniType() on each key & value. template inline ScopedJavaLocalRef ToJniType(JNIEnv* env, const ContainerType& map) { using KeyType = ContainerType::key_type; using ValueType = ContainerType::mapped_type; jsize map_jsize = static_cast(map.size()); jobjectArray j_array = env->NewObjectArray(map_jsize * 2, g_object_class, nullptr); CheckException(env); jsize i = 0; for (auto const& [key, value] : map) { // Do not call ToJni for jobject->jobject. if constexpr (internal::IsJavaRef) { env->SetObjectArrayElement(j_array, i, key.obj()); } else { ScopedJavaLocalRef j_key = ToJniType(env, key); env->SetObjectArrayElement(j_array, i, j_key.obj()); } ++i; if constexpr (internal::IsJavaRef) { env->SetObjectArrayElement(j_array, i, value.obj()); } else { ScopedJavaLocalRef j_value = ToJniType(env, value); env->SetObjectArrayElement(j_array, i, j_value.obj()); } ++i; } auto array = ScopedJavaLocalRef::Adopt(env, j_array); return ArrayToMap(env, array); } } // namespace jni_zero #endif // JNI_ZERO_DEFAULT_CONVERSIONS_H_