ledger-core
Marshal.hpp
1 //
2 // Copyright 2014 Dropbox, Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #pragma once
18 
19 #include "djinni_support.hpp"
20 #include <cassert>
21 #include <chrono>
22 #include <cstdint>
23 #include <string>
24 #include <unordered_map>
25 #include <unordered_set>
26 #include <vector>
27 
28 namespace djinni
29 {
30  template<class Self, class CppT, class JniT>
31  class Primitive
32  {
33  public:
34  using CppType = CppT;
35  using JniType = JniT;
36 
37  static CppType toCpp(JNIEnv* /*jniEnv*/, JniType j) noexcept { return static_cast<CppType>(j); }
38  static JniType fromCpp(JNIEnv* /*jniEnv*/, CppType c) noexcept { return static_cast<JniType>(c); }
39 
40  struct Boxed
41  {
42  using JniType = jobject;
43  static CppType toCpp(JNIEnv* jniEnv, JniType j)
44  {
45  assert(j != nullptr);
46  const auto& data = JniClass<Self>::get();
47  assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
48  auto ret = Primitive::toCpp(jniEnv, Self::unbox(jniEnv, data.method_unbox, j));
49  jniExceptionCheck(jniEnv);
50  return ret;
51  }
52  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, CppType c)
53  {
54  const auto& data = JniClass<Self>::get();
55  auto ret = jniEnv->CallStaticObjectMethod(data.clazz.get(), data.method_box, Primitive::fromCpp(jniEnv, c));
56  jniExceptionCheck(jniEnv);
57  return {jniEnv, ret};
58  }
59  };
60 
61  protected:
62  Primitive(const char* javaClassSpec,
63  const char* staticBoxMethod,
64  const char* staticBoxMethodSignature,
65  const char* unboxMethod,
66  const char* unboxMethodSignature)
67  : clazz(jniFindClass(javaClassSpec))
68  , method_box(jniGetStaticMethodID(clazz.get(), staticBoxMethod, staticBoxMethodSignature))
69  , method_unbox(jniGetMethodID(clazz.get(), unboxMethod, unboxMethodSignature))
70  {}
71 
72  private:
73  const GlobalRef<jclass> clazz;
74  const jmethodID method_box;
75  const jmethodID method_unbox;
76  };
77 
78  class Bool : public Primitive<Bool, bool, jboolean>
79  {
80  Bool() : Primitive("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", "booleanValue", "()Z") {}
81  friend JniClass<Bool>;
83  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
84  auto result = jniEnv->CallBooleanMethod(j, method);
85  jniExceptionCheck(jniEnv);
86  return result;
87  }
88  };
89 
90  class I8 : public Primitive<I8, int8_t, jbyte>
91  {
92  I8() : Primitive("java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", "byteValue", "()B") {}
93  friend JniClass<I8>;
95  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
96  auto result = jniEnv->CallByteMethod(j, method);
97  jniExceptionCheck(jniEnv);
98  return result;
99  }
100  };
101 
102  class I16 : public Primitive<I16, int16_t, jshort>
103  {
104  I16() : Primitive("java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", "shortValue", "()S") {}
105  friend JniClass<I16>;
107  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
108  auto result = jniEnv->CallShortMethod(j, method);
109  jniExceptionCheck(jniEnv);
110  return result;
111  }
112  };
113 
114  class I32 : public Primitive<I32, int32_t, jint>
115  {
116  I32() : Primitive("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", "intValue", "()I") {}
117  friend JniClass<I32>;
119  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
120  auto result = jniEnv->CallIntMethod(j, method);
121  jniExceptionCheck(jniEnv);
122  return result;
123  }
124  };
125 
126  class I64 : public Primitive<I64, int64_t, jlong>
127  {
128  I64() : Primitive("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", "longValue", "()J") {}
129  friend JniClass<I64>;
131  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
132  auto result = jniEnv->CallLongMethod(j, method);
133  jniExceptionCheck(jniEnv);
134  return result;
135  }
136  };
137 
138  class F32 : public Primitive<F32, float, jfloat>
139  {
140  F32() : Primitive("java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", "floatValue", "()F") {}
141  friend JniClass<F32>;
143  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
144  auto result = jniEnv->CallFloatMethod(j, method);
145  jniExceptionCheck(jniEnv);
146  return result;
147  }
148  };
149 
150  class F64 : public Primitive<F64, double, jdouble>
151  {
152  F64() : Primitive("java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", "doubleValue", "()D") {}
153  friend JniClass<F64>;
155  static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
156  auto result = jniEnv->CallDoubleMethod(j, method);
157  jniExceptionCheck(jniEnv);
158  return result;
159  }
160  };
161 
162  struct String
163  {
164  using CppType = std::string;
165  using JniType = jstring;
166 
167  using Boxed = String;
168 
169  static CppType toCpp(JNIEnv* jniEnv, JniType j)
170  {
171  assert(j != nullptr);
172  return jniUTF8FromString(jniEnv, j);
173  }
174 
175  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
176  {
177  return {jniEnv, jniStringFromUTF8(jniEnv, c)};
178  }
179  };
180 
181  struct WString
182  {
183  using CppType = std::wstring;
184  using JniType = jstring;
185 
186  using Boxed = WString;
187 
188  static CppType toCpp(JNIEnv* jniEnv, JniType j)
189  {
190  assert(j != nullptr);
191  return jniWStringFromString(jniEnv, j);
192  }
193 
194  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
195  {
196  return {jniEnv, jniStringFromWString(jniEnv, c)};
197  }
198  };
199 
200  struct Binary
201  {
202  using CppType = std::vector<uint8_t>;
203  using JniType = jbyteArray;
204 
205  using Boxed = Binary;
206 
207  static CppType toCpp(JNIEnv* jniEnv, JniType j)
208  {
209  assert(j != nullptr);
210 
211  std::vector<uint8_t> ret;
212  jsize length = jniEnv->GetArrayLength(j);
213  jniExceptionCheck(jniEnv);
214 
215  if (!length) {
216  return ret;
217  }
218 
219  {
220  auto deleter = [jniEnv, j] (void* c) {
221  if (c) {
222  jniEnv->ReleasePrimitiveArrayCritical(j, c, JNI_ABORT);
223  }
224  };
225 
226  std::unique_ptr<uint8_t, decltype(deleter)> ptr(
227  reinterpret_cast<uint8_t*>(jniEnv->GetPrimitiveArrayCritical(j, nullptr)),
228  deleter
229  );
230 
231  if (ptr) {
232  // Construct and then move-assign. This copies the elements only once,
233  // and avoids having to initialize before filling (as with resize())
234  ret = std::vector<uint8_t>{ptr.get(), ptr.get() + length};
235  } else {
236  // Something failed...
237  jniExceptionCheck(jniEnv);
238  }
239  }
240 
241  return ret;
242  }
243 
244  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
245  {
246  assert(c.size() <= std::numeric_limits<jsize>::max());
247  auto j = LocalRef<jbyteArray>(jniEnv, jniEnv->NewByteArray(static_cast<jsize>(c.size())));
248  jniExceptionCheck(jniEnv);
249  // Using .data() on an empty vector is UB
250  if(!c.empty())
251  {
252  jniEnv->SetByteArrayRegion(j.get(), 0, jsize(c.size()), reinterpret_cast<const jbyte*>(c.data()));
253  }
254  return j;
255  }
256  };
257 
258  struct Date
259  {
260  using CppType = std::chrono::system_clock::time_point;
261  using JniType = jobject;
262 
263  using Boxed = Date;
264 
265  static CppType toCpp(JNIEnv* jniEnv, JniType j)
266  {
267  static const auto POSIX_EPOCH = std::chrono::system_clock::from_time_t(0);
268  assert(j != nullptr);
269  const auto & data = JniClass<Date>::get();
270  assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
271  auto time_millis = jniEnv->CallLongMethod(j, data.method_get_time);
272  jniExceptionCheck(jniEnv);
273  return POSIX_EPOCH + std::chrono::milliseconds{time_millis};
274  }
275 
276  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
277  {
278  static const auto POSIX_EPOCH = std::chrono::system_clock::from_time_t(0);
279  const auto & data = JniClass<Date>::get();
280  const auto cpp_millis = std::chrono::duration_cast<std::chrono::milliseconds>(c - POSIX_EPOCH);
281  const jlong millis = static_cast<jlong>(cpp_millis.count());
282  auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, millis));
283  jniExceptionCheck(jniEnv);
284  return j;
285  }
286 
287  private:
288  Date() = default;
289  friend ::djinni::JniClass<Date>;
290 
291  const GlobalRef<jclass> clazz { jniFindClass("java/util/Date") };
292  const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "(J)V") };
293  const jmethodID method_get_time { jniGetMethodID(clazz.get(), "getTime", "()J") };
294  };
295 
296  template <template <class> class OptionalType, class T>
297  struct Optional
298  {
299  // SFINAE helper: if C::CppOptType exists, opt_type<T>(nullptr) will return
300  // that type. If not, it returns OptionalType<C::CppType>. This is necessary
301  // because we special-case optional interfaces to be represented as a nullable
302  // std::shared_ptr<T>, not optional<shared_ptr<T>> or optional<nn<shared_ptr<T>>>.
303  template <typename C> static OptionalType<typename C::CppType> opt_type(...);
304  template <typename C> static typename C::CppOptType opt_type(typename C::CppOptType *);
305  using CppType = decltype(opt_type<T>(nullptr));
306 
307  using JniType = typename T::Boxed::JniType;
308 
309  using Boxed = Optional;
310 
311  static CppType toCpp(JNIEnv* jniEnv, JniType j)
312  {
313  if (j) {
314  return T::Boxed::toCpp(jniEnv, j);
315  } else {
316  return CppType();
317  }
318  }
319 
320  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const OptionalType<typename T::CppType> &c)
321  {
322  return c ? T::Boxed::fromCpp(jniEnv, *c) : LocalRef<JniType>{};
323  }
324 
325  // fromCpp used for nullable shared_ptr
326  template <typename C = T>
327  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const typename C::CppOptType & cppOpt) {
328  return T::Boxed::fromCppOpt(jniEnv, cppOpt);
329  }
330  };
331 
332  struct ListJniInfo
333  {
334  const GlobalRef<jclass> clazz { jniFindClass("java/util/ArrayList") };
335  const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "(I)V") };
336  const jmethodID method_add { jniGetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z") };
337  const jmethodID method_get { jniGetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;") };
338  const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
339  };
340 
341  template <class T>
342  class List
343  {
344  using ECppType = typename T::CppType;
345  using EJniType = typename T::Boxed::JniType;
346 
347  public:
348  using CppType = std::vector<ECppType>;
349  using JniType = jobject;
350 
351  using Boxed = List;
352 
353  static CppType toCpp(JNIEnv* jniEnv, JniType j)
354  {
355  assert(j != nullptr);
356  const auto& data = JniClass<ListJniInfo>::get();
357  assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
358  auto size = jniEnv->CallIntMethod(j, data.method_size);
359  jniExceptionCheck(jniEnv);
360  auto c = CppType();
361  c.reserve(size);
362  for(jint i = 0; i < size; ++i)
363  {
364  auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_get, i));
365  jniExceptionCheck(jniEnv);
366  c.push_back(T::Boxed::toCpp(jniEnv, static_cast<EJniType>(je.get())));
367  }
368  return c;
369  }
370 
371  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
372  {
373  const auto& data = JniClass<ListJniInfo>::get();
374  assert(c.size() <= std::numeric_limits<jint>::max());
375  auto size = static_cast<jint>(c.size());
376  auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
377  jniExceptionCheck(jniEnv);
378  for(const auto& ce : c)
379  {
380  auto je = T::Boxed::fromCpp(jniEnv, ce);
381  jniEnv->CallBooleanMethod(j, data.method_add, get(je));
382  jniExceptionCheck(jniEnv);
383  }
384  return j;
385  }
386  };
387 
389  {
390  const GlobalRef<jclass> clazz { jniFindClass("java/util/Iterator") };
391  const jmethodID method_next { jniGetMethodID(clazz.get(), "next", "()Ljava/lang/Object;") };
392  };
393 
394  struct SetJniInfo
395  {
396  const GlobalRef<jclass> clazz { jniFindClass("java/util/HashSet") };
397  const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "()V") };
398  const jmethodID method_add { jniGetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z") };
399  const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
400  const jmethodID method_iterator { jniGetMethodID(clazz.get(), "iterator", "()Ljava/util/Iterator;") };
401  };
402 
403  template <class T>
404  class Set
405  {
406  using ECppType = typename T::CppType;
407  using EJniType = typename T::Boxed::JniType;
408 
409  public:
410  using CppType = std::unordered_set<ECppType>;
411  using JniType = jobject;
412 
413  using Boxed = Set;
414 
415  static CppType toCpp(JNIEnv* jniEnv, JniType j)
416  {
417  assert(j != nullptr);
418  const auto& data = JniClass<SetJniInfo>::get();
419  const auto& iteData = JniClass<IteratorJniInfo>::get();
420  assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
421  auto size = jniEnv->CallIntMethod(j, data.method_size);
422  jniExceptionCheck(jniEnv);
423  auto c = CppType();
424  auto it = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_iterator));
425  jniExceptionCheck(jniEnv);
426  for(jint i = 0; i < size; ++i)
427  {
428  auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(it, iteData.method_next));
429  jniExceptionCheck(jniEnv);
430  c.insert(T::Boxed::toCpp(jniEnv, static_cast<EJniType>(je.get())));
431  }
432  return c;
433  }
434 
435  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
436  {
437  const auto& data = JniClass<SetJniInfo>::get();
438  assert(c.size() <= std::numeric_limits<jint>::max());
439  auto size = static_cast<jint>(c.size());
440  auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
441  jniExceptionCheck(jniEnv);
442  for(const auto& ce : c)
443  {
444  auto je = T::Boxed::fromCpp(jniEnv, ce);
445  jniEnv->CallBooleanMethod(j, data.method_add, get(je));
446  jniExceptionCheck(jniEnv);
447  }
448  return j;
449  }
450  };
451 
452  struct MapJniInfo
453  {
454  const GlobalRef<jclass> clazz { jniFindClass("java/util/HashMap") };
455  const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "()V") };
456  const jmethodID method_put { jniGetMethodID(clazz.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") };
457  const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
458  const jmethodID method_entrySet { jniGetMethodID(clazz.get(), "entrySet", "()Ljava/util/Set;") };
459  };
460 
462  {
463  const GlobalRef<jclass> clazz { jniFindClass("java/util/Set") };
464  const jmethodID method_iterator { jniGetMethodID(clazz.get(), "iterator", "()Ljava/util/Iterator;") };
465  };
466 
468  {
469  const GlobalRef<jclass> clazz { jniFindClass("java/util/Map$Entry") };
470  const jmethodID method_getKey { jniGetMethodID(clazz.get(), "getKey", "()Ljava/lang/Object;") };
471  const jmethodID method_getValue { jniGetMethodID(clazz.get(), "getValue", "()Ljava/lang/Object;") };
472  };
473 
474  template <class Key, class Value>
475  class Map
476  {
477  using CppKeyType = typename Key::CppType;
478  using CppValueType = typename Value::CppType;
479  using JniKeyType = typename Key::Boxed::JniType;
480  using JniValueType = typename Value::Boxed::JniType;
481 
482  public:
483  using CppType = std::unordered_map<CppKeyType, CppValueType>;
484  using JniType = jobject;
485 
486  using Boxed = Map;
487 
488  static CppType toCpp(JNIEnv* jniEnv, JniType j)
489  {
490  assert(j != nullptr);
491  const auto& data = JniClass<MapJniInfo>::get();
492  const auto& entrySetData = JniClass<EntrySetJniInfo>::get();
493  const auto& entryData = JniClass<EntryJniInfo>::get();
494  const auto& iteData = JniClass<IteratorJniInfo>::get();
495  assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
496  auto size = jniEnv->CallIntMethod(j, data.method_size);
497  jniExceptionCheck(jniEnv);
498  auto entrySet = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_entrySet));
499  jniExceptionCheck(jniEnv);
500  auto c = CppType();
501  c.reserve(size);
502  auto it = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(entrySet, entrySetData.method_iterator));
503  jniExceptionCheck(jniEnv);
504  for(jint i = 0; i < size; ++i)
505  {
506  auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(it, iteData.method_next));
507  jniExceptionCheck(jniEnv);
508  auto jKey = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(je, entryData.method_getKey));
509  jniExceptionCheck(jniEnv);
510  auto jValue = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(je, entryData.method_getValue));
511  jniExceptionCheck(jniEnv);
512  c.emplace(Key::Boxed::toCpp(jniEnv, static_cast<JniKeyType>(jKey.get())),
513  Value::Boxed::toCpp(jniEnv, static_cast<JniValueType>(jValue.get())));
514  }
515  return c;
516  }
517 
518  static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
519  {
520  const auto& data = JniClass<MapJniInfo>::get();
521  assert(c.size() <= std::numeric_limits<jint>::max());
522  auto size = c.size();
523  auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
524  jniExceptionCheck(jniEnv);
525  for(const auto& ce : c)
526  {
527  auto jKey = Key::Boxed::fromCpp(jniEnv, ce.first);
528  auto jValue = Value::Boxed::fromCpp(jniEnv, ce.second);
529  jniEnv->CallObjectMethod(j, data.method_put, get(jKey), get(jValue));
530  jniExceptionCheck(jniEnv);
531  }
532  return j;
533  }
534  };
535 
536 } // namespace djinni
Definition: Marshal.hpp:452
Definition: Marshal.hpp:181
Definition: Marshal.hpp:297
Definition: Marshal.hpp:102
Definition: Marshal.hpp:467
Definition: djinni_support.hpp:80
Definition: Marshal.hpp:342
Definition: Marshal.hpp:200
Definition: Marshal.hpp:40
Definition: Marshal.hpp:90
Definition: Marshal.hpp:332
Definition: Marshal.hpp:404
Definition: Marshal.hpp:475
Definition: Marshal.hpp:78
Definition: Marshal.hpp:114
Definition: Marshal.hpp:138
Definition: djinni_support.cpp:27
Definition: Marshal.hpp:258
Definition: djinni_support.hpp:223
Definition: Marshal.hpp:126
Definition: Marshal.hpp:150
Definition: Marshal.hpp:394
Definition: Marshal.hpp:388
Definition: Marshal.hpp:162
Definition: Marshal.hpp:31
Definition: Marshal.hpp:461