1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef JNI_ZERO_DEFAULT_CONVERSIONS_H_
6 #define JNI_ZERO_DEFAULT_CONVERSIONS_H_
7
8 #include <optional>
9 #include <type_traits>
10 #include <vector>
11
12 #include "third_party/jni_zero/common_apis.h"
13 #include "third_party/jni_zero/jni_zero.h"
14
15 namespace jni_zero {
16 namespace internal {
17 template <typename T>
18 concept IsJavaRef = std::is_base_of_v<JavaRef<jobject>, T>;
19
20 template <typename T>
requires(T t)21 concept HasReserve = requires(T t) { t.reserve(0); };
22
23 template <typename T>
requires(T t,T::value_type v)24 concept HasPushBack = requires(T t, T::value_type v) { t.push_back(v); };
25
26 template <typename T>
requires(T t,T::value_type v)27 concept HasInsert = requires(T t, T::value_type v) { t.insert(v); };
28
29 template <typename T>
requires(T t)30 concept IsMap = requires(T t) {
31 typename T::key_type;
32 typename T::mapped_type;
33 };
34
35 template <typename T>
requires(T t)36 concept IsContainer = requires(T t) {
37 !IsMap<T>;
38 typename T::value_type;
39 t.begin();
40 t.end();
41 t.size();
42 };
43
44 template <typename T>
45 concept IsObjectContainer =
46 IsContainer<T> && !std::is_arithmetic_v<typename T::value_type>;
47
48 template <typename T>
49 concept IsOptional = std::same_as<T, std::optional<typename T::value_type>>;
50 } // namespace internal
51
52 // Allow conversions using std::optional by wrapping non-optional conversions.
53 template <internal::IsOptional T>
FromJniType(JNIEnv * env,const JavaRef<jobject> & j_object)54 inline T FromJniType(JNIEnv* env, const JavaRef<jobject>& j_object) {
55 if (!j_object) {
56 return std::nullopt;
57 }
58 return FromJniType<typename T::value_type>(env, j_object);
59 }
60
61 template <internal::IsOptional T>
ToJniType(JNIEnv * env,const T & opt_value)62 inline ScopedJavaLocalRef<jobject> ToJniType(JNIEnv* env, const T& opt_value) {
63 if (!opt_value) {
64 return nullptr;
65 }
66 return ToJniType(env, opt_value.value());
67 }
68
69 // Convert Java array -> container type using FromJniType() on each element.
70 template <internal::IsObjectContainer ContainerType>
FromJniArray(JNIEnv * env,const JavaRef<jobject> & j_object)71 inline ContainerType FromJniArray(JNIEnv* env,
72 const JavaRef<jobject>& j_object) {
73 jobjectArray j_array = static_cast<jobjectArray>(j_object.obj());
74 using ElementType = std::remove_const_t<typename ContainerType::value_type>;
75 constexpr bool has_push_back = internal::HasPushBack<ContainerType>;
76 constexpr bool has_insert = internal::HasInsert<ContainerType>;
77 static_assert(has_push_back || has_insert, "Template type not supported.");
78 jsize array_jsize = env->GetArrayLength(j_array);
79
80 ContainerType ret;
81 if constexpr (internal::HasReserve<ContainerType>) {
82 size_t array_size = static_cast<size_t>(array_jsize);
83 ret.reserve(array_size);
84 }
85 for (jsize i = 0; i < array_jsize; ++i) {
86 jobject j_element = env->GetObjectArrayElement(j_array, i);
87 // Do not call FromJni for jobject->jobject.
88 if constexpr (std::is_base_of_v<JavaRef<jobject>, ElementType>) {
89 if constexpr (has_push_back) {
90 ret.emplace_back(env, j_element);
91 } else if constexpr (has_insert) {
92 ret.emplace(env, j_element);
93 }
94 } else {
95 auto element = ScopedJavaLocalRef<jobject>::Adopt(env, j_element);
96 if constexpr (has_push_back) {
97 ret.push_back(FromJniType<ElementType>(env, element));
98 } else if constexpr (has_insert) {
99 ret.insert(FromJniType<ElementType>(env, element));
100 }
101 }
102 }
103 return ret;
104 }
105
106 // Convert container type -> Java array using ToJniType() on each element.
107 template <internal::IsObjectContainer ContainerType>
108 inline ScopedJavaLocalRef<jobjectArray>
ToJniArray(JNIEnv * env,const ContainerType & collection,jclass clazz)109 ToJniArray(JNIEnv* env, const ContainerType& collection, jclass clazz) {
110 using ElementType = std::remove_const_t<typename ContainerType::value_type>;
111 size_t array_size = collection.size();
112 jsize array_jsize = static_cast<jsize>(array_size);
113 jobjectArray j_array = env->NewObjectArray(array_jsize, clazz, nullptr);
114 CheckException(env);
115
116 jsize i = 0;
117 for (auto& value : collection) {
118 // Do not call ToJni for jobject->jobject.
119 if constexpr (std::is_base_of_v<JavaRef<jobject>, ElementType>) {
120 env->SetObjectArrayElement(j_array, i, value.obj());
121 } else {
122 ScopedJavaLocalRef<jobject> element = ToJniType(env, value);
123 env->SetObjectArrayElement(j_array, i, element.obj());
124 }
125 ++i;
126 }
127 return ScopedJavaLocalRef<jobjectArray>(env, j_array);
128 }
129
130 #define DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(T) \
131 template <> \
132 JNI_ZERO_COMPONENT_BUILD_EXPORT std::vector<T> FromJniArray<std::vector<T>>( \
133 JNIEnv * env, const JavaRef<jobject>& j_object); \
134 template <> \
135 JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef<jarray> \
136 ToJniArray<std::vector<T>>(JNIEnv * env, const std::vector<T>& vec);
137
138 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int64_t)
DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int32_t)139 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int32_t)
140 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(int16_t)
141 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(uint16_t)
142 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(uint8_t)
143 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(bool)
144 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(float)
145 DECLARE_PRIMITIVE_ARRAY_CONVERSIONS(double)
146
147 #undef DECLARE_PRIMITIVE_ARRAY_CONVERSIONS
148
149 // Specialization for ByteArrayView.
150 template <>
151 inline ByteArrayView FromJniArray<ByteArrayView>(
152 JNIEnv* env,
153 const JavaRef<jobject>& j_object) {
154 jbyteArray j_array = static_cast<jbyteArray>(j_object.obj());
155 return ByteArrayView(env, j_array);
156 }
157
158 // There is a circular dependency between common_apis.cc and here.
159 JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef<jobjectArray>
160 CollectionToArray(JNIEnv* env, const JavaRef<jobject>& collection);
161
162 JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef<jobject> ArrayToList(
163 JNIEnv* env,
164 const JavaRef<jobjectArray>& array);
165
166 template <internal::IsObjectContainer ContainerType>
FromJniCollection(JNIEnv * env,const JavaRef<jobject> & j_collection)167 inline ContainerType FromJniCollection(JNIEnv* env,
168 const JavaRef<jobject>& j_collection) {
169 ScopedJavaLocalRef<jobjectArray> arr = CollectionToArray(env, j_collection);
170 return FromJniArray<ContainerType>(env, arr);
171 }
172
173 // Convert container type -> Java array using ToJniType() on each element.
174 template <internal::IsObjectContainer ContainerType>
ToJniList(JNIEnv * env,const ContainerType & collection)175 inline ScopedJavaLocalRef<jobject> ToJniList(JNIEnv* env,
176 const ContainerType& collection) {
177 ScopedJavaLocalRef<jobjectArray> arr =
178 ToJniArray(env, collection, g_object_class);
179 return ArrayToList(env, arr);
180 }
181
182 JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef<jobjectArray> MapToArray(
183 JNIEnv* env,
184 const JavaRef<jobject>& map);
185 JNI_ZERO_COMPONENT_BUILD_EXPORT ScopedJavaLocalRef<jobject> ArrayToMap(
186 JNIEnv* env,
187 const JavaRef<jobjectArray>& array);
188
189 // Convert Map -> stl map type using FromJniType() on each key & value.
190 template <internal::IsMap ContainerType>
FromJniType(JNIEnv * env,const JavaRef<jobject> & j_object)191 inline ContainerType FromJniType(JNIEnv* env,
192 const JavaRef<jobject>& j_object) {
193 using KeyType = ContainerType::key_type;
194 using ValueType = ContainerType::mapped_type;
195
196 ScopedJavaLocalRef<jobjectArray> j_array = MapToArray(env, j_object);
197 jsize array_jsize = env->GetArrayLength(j_array.obj());
198
199 ContainerType ret;
200 if constexpr (internal::HasReserve<ContainerType>) {
201 size_t array_size = static_cast<size_t>(array_jsize);
202 ret.reserve(array_size / 2);
203 }
204 for (jsize i = 0; i < array_jsize; i += 2) {
205 // No need to call CheckException() since we know the array is of the
206 // correct size, since we are the ones who created it.
207 jobject j_key = env->GetObjectArrayElement(j_array.obj(), i);
208 jobject j_value = env->GetObjectArrayElement(j_array.obj(), i + 1);
209 // Do not call FromJni for jobject->jobject.
210 if constexpr (internal::IsJavaRef<KeyType> &&
211 internal::IsJavaRef<ValueType>) {
212 ret.emplace(std::piecewise_construct, std::forward_as_tuple(env, j_key),
213 std::forward_as_tuple(env, j_value));
214 } else if constexpr (internal::IsJavaRef<KeyType>) {
215 auto value = ScopedJavaLocalRef<jobject>::Adopt(env, j_value);
216 ret.emplace(std::piecewise_construct, std::forward_as_tuple(env, j_key),
217 FromJniType<ValueType>(env, value));
218 } else if constexpr (internal::IsJavaRef<ValueType>) {
219 auto key = ScopedJavaLocalRef<jobject>::Adopt(env, j_key);
220 ret.emplace(std::piecewise_construct, FromJniType<KeyType>(env, key),
221 std::forward_as_tuple(env, j_value));
222 } else {
223 auto key = ScopedJavaLocalRef<jobject>::Adopt(env, j_key);
224 auto value = ScopedJavaLocalRef<jobject>::Adopt(env, j_value);
225 ret.emplace(FromJniType<KeyType>(env, key),
226 FromJniType<ValueType>(env, value));
227 }
228 }
229 return ret;
230 }
231
232 // Convert stl map -> Map type using ToJniType() on each key & value.
233 template <internal::IsMap ContainerType>
ToJniType(JNIEnv * env,const ContainerType & map)234 inline ScopedJavaLocalRef<jobject> ToJniType(JNIEnv* env,
235 const ContainerType& map) {
236 using KeyType = ContainerType::key_type;
237 using ValueType = ContainerType::mapped_type;
238 jsize map_jsize = static_cast<jsize>(map.size());
239 jobjectArray j_array =
240 env->NewObjectArray(map_jsize * 2, g_object_class, nullptr);
241 CheckException(env);
242
243 jsize i = 0;
244 for (auto const& [key, value] : map) {
245 // Do not call ToJni for jobject->jobject.
246 if constexpr (internal::IsJavaRef<KeyType>) {
247 env->SetObjectArrayElement(j_array, i, key.obj());
248 } else {
249 ScopedJavaLocalRef<jobject> j_key = ToJniType(env, key);
250 env->SetObjectArrayElement(j_array, i, j_key.obj());
251 }
252 ++i;
253
254 if constexpr (internal::IsJavaRef<ValueType>) {
255 env->SetObjectArrayElement(j_array, i, value.obj());
256 } else {
257 ScopedJavaLocalRef<jobject> j_value = ToJniType(env, value);
258 env->SetObjectArrayElement(j_array, i, j_value.obj());
259 }
260 ++i;
261 }
262 auto array = ScopedJavaLocalRef<jobjectArray>::Adopt(env, j_array);
263 return ArrayToMap(env, array);
264 }
265 } // namespace jni_zero
266 #endif // JNI_ZERO_DEFAULT_CONVERSIONS_H_
267