WPILibC++ 2024.3.2
jni_util.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#ifndef WPIUTIL_WPI_JNI_UTIL_H_
6#define WPIUTIL_WPI_JNI_UTIL_H_
7
8#include <jni.h>
9
10#include <concepts>
11#include <queue>
12#include <span>
13#include <string>
14#include <string_view>
15#include <utility>
16#include <vector>
17
18#include <fmt/core.h>
19
20#include "wpi/ConvertUTF.h"
21#include "wpi/SafeThread.h"
22#include "wpi/SmallString.h"
23#include "wpi/SmallVector.h"
24#include "wpi/StringExtras.h"
25#include "wpi/mutex.h"
26#include "wpi/raw_ostream.h"
27
28/** Java Native Interface (JNI) utility functions */
29namespace wpi::java {
30
31/**
32 * Gets a Java stack trace.
33 *
34 * Also provides the last function in the stack trace not starting with
35 * excludeFuncPrefix (useful for e.g. finding the first user call to a series of
36 * library functions).
37 *
38 * @param env JRE environment.
39 * @param func Storage for last function in the stack trace not starting with
40 * excludeFuncPrefix.
41 * @param excludeFuncPrefix Prefix for functions to ignore in stack trace.
42 */
43std::string GetJavaStackTrace(JNIEnv* env, std::string* func = nullptr,
44 std::string_view excludeFuncPrefix = {});
45
46/**
47 * Finds a class and keeps it as a global reference.
48 *
49 * Use with caution, as the destructor does NOT call DeleteGlobalRef due to
50 * potential shutdown issues with doing so.
51 */
52class JClass {
53 public:
54 JClass() = default;
55
56 JClass(JNIEnv* env, const char* name) {
57 jclass local = env->FindClass(name);
58 if (!local) {
59 return;
60 }
61 m_cls = static_cast<jclass>(env->NewGlobalRef(local));
62 env->DeleteLocalRef(local);
63 }
64
65 void free(JNIEnv* env) {
66 if (m_cls) {
67 env->DeleteGlobalRef(m_cls);
68 }
69 m_cls = nullptr;
70 }
71
72 explicit operator bool() const { return m_cls; }
73
74 operator jclass() const { return m_cls; }
75
76 protected:
77 jclass m_cls = nullptr;
78};
79
80struct JClassInit {
81 const char* name;
83};
84
85template <typename T>
86class JGlobal {
87 public:
88 JGlobal() = default;
89
90 JGlobal(JNIEnv* env, T obj) {
91 m_cls = static_cast<T>(env->NewGlobalRef(obj));
92 }
93
94 void free(JNIEnv* env) {
95 if (m_cls) {
96 env->DeleteGlobalRef(m_cls);
97 }
98 m_cls = nullptr;
99 }
100
101 explicit operator bool() const { return m_cls; }
102
103 operator T() const { return m_cls; } // NOLINT
104
105 protected:
106 T m_cls = nullptr;
107};
108
109/**
110 * Container class for cleaning up Java local references.
111 *
112 * The destructor calls DeleteLocalRef.
113 */
114template <typename T>
115class JLocal {
116 public:
117 JLocal(JNIEnv* env, T obj) : m_env(env), m_obj(obj) {}
118 JLocal(const JLocal&) = delete;
119 JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
120 oth.m_obj = nullptr;
121 }
122 JLocal& operator=(const JLocal&) = delete;
124 m_env = oth.m_env;
125 m_obj = oth.m_obj;
126 oth.m_obj = nullptr;
127 return *this;
128 }
130 if (m_obj) {
131 m_env->DeleteLocalRef(m_obj);
132 }
133 }
134 operator T() { return m_obj; } // NOLINT
135 T obj() { return m_obj; }
136
137 private:
138 JNIEnv* m_env;
139 T m_obj;
140};
141
142//
143// Conversions from Java objects to C++
144//
145
146/**
147 * Java string (jstring) reference.
148 *
149 * The string is provided as UTF8. This is not actually a reference, as it makes
150 * a copy of the string characters, but it's named this way for consistency.
151 */
153 public:
154 JStringRef(JNIEnv* env, jstring str) {
155 if (str) {
156 jsize size = env->GetStringLength(str);
157 const jchar* chars = env->GetStringCritical(str, nullptr);
158 if (chars) {
159 convertUTF16ToUTF8String(std::span<const jchar>(chars, size), m_str);
160 env->ReleaseStringCritical(str, chars);
161 }
162 } else {
163 fmt::print(stderr, "JStringRef was passed a null pointer at\n",
164 GetJavaStackTrace(env));
165 }
166 // Ensure str is null-terminated.
167 m_str.push_back('\0');
168 m_str.pop_back();
169 }
170
171 operator std::string_view() const { return m_str.str(); } // NOLINT
172 std::string_view str() const { return m_str.str(); }
173 const char* c_str() const { return m_str.data(); }
174 size_t size() const { return m_str.size(); }
175
176 private:
177 SmallString<128> m_str;
178};
179
180namespace detail {
181
182template <typename T>
183struct ArrayHelper {};
184
185#define WPI_JNI_ARRAYHELPER(T, F) \
186 template <> \
187 struct ArrayHelper<T> { \
188 using jarray_type = T##Array; \
189 static T* GetArrayElements(JNIEnv* env, jarray_type jarr) { \
190 return env->Get##F##ArrayElements(jarr, nullptr); \
191 } \
192 static void ReleaseArrayElements(JNIEnv* env, jarray_type jarr, T* elems, \
193 jint mode) { \
194 env->Release##F##ArrayElements(jarr, elems, mode); \
195 } \
196 };
197
199WPI_JNI_ARRAYHELPER(jbyte, Byte)
200WPI_JNI_ARRAYHELPER(jshort, Short)
201WPI_JNI_ARRAYHELPER(jint, Int)
202WPI_JNI_ARRAYHELPER(jlong, Long)
203WPI_JNI_ARRAYHELPER(jfloat, Float)
204WPI_JNI_ARRAYHELPER(jdouble, Double)
205
206#undef WPI_JNI_ARRAYHELPER
207
208template <typename T>
209concept JArrayType =
210 requires { typename ArrayHelper<std::remove_cv_t<T>>::jarray_type; };
211
212template <typename CvSrc, typename Dest>
213struct copy_cv {
214 private:
215 using U0 = std::remove_cv_t<Dest>;
216 using U1 = std::conditional_t<std::is_const_v<CvSrc>, const U0, U0>;
217 using U2 = std::conditional_t<std::is_volatile_v<CvSrc>, volatile U1, U1>;
218
219 public:
220 using type = U2;
221};
222
223template <typename CvSrc, typename Dest>
225
226template <typename From, typename To>
228 !(std::is_const_v<From> && !std::is_const_v<To>)&&!(
229 std::is_volatile_v<From> && !std::is_volatile_v<To>);
230
231/**
232 * Helper class for working with JNI arrays.
233 *
234 * This class exposes an is_valid() member and an explicit conversion to bool
235 * which indicate if the span is valid. Operations on invalid spans are
236 * undefined.
237 *
238 * Note that Set<PrimitiveType>ArrayRegion may be faster for pure writes since
239 * it avoids copying the elements from Java to C++.
240 *
241 * @tparam T The element type of the array (e.g., jdouble).
242 * @tparam IsCritical If true, Get/ReleasePrimitiveArrayCritical will be used
243 * instead of Get/Release<PrimitiveType>ArrayElements.
244 * @tparam Size The number of elements in the span.
245 */
246template <JArrayType T, bool IsCritical, size_t Size = std::dynamic_extent>
249 using jarray_type = typename ArrHelper::jarray_type;
250
251 public:
252 JSpanBase(const JSpanBase&) = delete;
253 JSpanBase& operator=(const JSpanBase&) = delete;
254
256 : m_valid{other.m_valid},
257 m_env{other.m_env},
258 m_jarr{other.m_jarr},
259 m_size{other.m_size},
260 m_elements{other.m_elements} {
261 other.m_jarr = nullptr;
262 other.m_elements = nullptr;
263 }
264
266 m_valid = other.m_valid;
267 m_env = other.m_env;
268 m_jarr = other.m_jarr;
269 m_size = other.m_size;
270 m_elements = other.m_elements;
271 other.m_valid = false;
272 other.m_jarr = nullptr;
273 other.m_elements = nullptr;
274 return *this;
275 }
276
277 JSpanBase(JNIEnv* env, jobject bb, size_t size)
278 requires(!IsCritical)
279 : m_valid{Size == std::dynamic_extent || size == Size},
280 m_env{env},
281 m_jarr{nullptr},
282 m_size{size},
283 m_elements{static_cast<std::remove_cv_t<T>*>(
284 bb ? env->GetDirectBufferAddress(bb) : nullptr)} {
285 if (!bb) {
286 fmt::print(stderr, "JSpan was passed a null pointer at\n",
287 GetJavaStackTrace(env));
288 }
289 }
290
291 JSpanBase(JNIEnv* env, jarray_type jarr, size_t size)
292 : m_valid{Size == std::dynamic_extent || size == Size},
293 m_env{env},
294 m_jarr{jarr},
295 m_size{size},
296 m_elements{nullptr} {
297 if (jarr) {
298 if constexpr (IsCritical) {
299 m_elements = static_cast<std::remove_cv_t<T>*>(
300 env->GetPrimitiveArrayCritical(jarr, nullptr));
301 } else {
302 m_elements = ArrHelper::GetArrayElements(env, jarr);
303 }
304 } else {
305 fmt::print(stderr, "JSpan was passed a null pointer at\n",
306 GetJavaStackTrace(env));
307 }
308 }
309
310 JSpanBase(JNIEnv* env, jarray_type jarr)
311 : JSpanBase(env, jarr, jarr ? env->GetArrayLength(jarr) : 0) {}
312
314 if (m_jarr && m_elements) {
315 constexpr jint mode = std::is_const_v<T> ? JNI_ABORT : 0;
316 if constexpr (IsCritical) {
317 m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, mode);
318 } else {
319 ArrHelper::ReleaseArrayElements(m_env, m_jarr, m_elements, mode);
320 }
321 }
322 }
323
324 operator std::span<T, Size>() const { return array(); }
325
326 std::span<T, Size> array() const {
327 // If Size is dynamic_extent, can return empty span
328 // Unfortunately, sized spans will return a span over nullptr if m_elements
329 // is nullptr
330 if constexpr (Size == std::dynamic_extent) {
331 if (!m_elements) {
332 return {};
333 }
334 }
335 return std::span<T, Size>{m_elements, m_size};
336 }
337
338 T* begin() const { return m_elements; }
339
340 T* end() const { return m_elements + m_size; }
341
342 bool is_valid() const { return m_valid && m_elements != nullptr; }
343
344 explicit operator bool() const { return is_valid(); }
345
346 T* data() const { return m_elements; }
347
348 size_t size() const { return m_size; }
349
350 const T& operator[](size_t i) const { return m_elements[i]; }
351
352 T& operator[](size_t i)
353 requires(!std::is_const_v<T>)
354 {
355 return m_elements[i];
356 }
357
358 // Provide std::string_view and span<const uint8_t> conversions for jbyte
359
360 operator std::string_view() const
361 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
362 {
363 return str();
364 }
365
367 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
368 {
369 auto arr = array();
370 if (arr.empty()) {
371 return {};
372 }
373 return {reinterpret_cast<const char*>(arr.data()), arr.size()};
374 }
375
376 std::span<copy_cv_t<T, uint8_t>, Size> uarray() const
377 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
378 {
379 auto arr = array();
380 if (arr.empty()) {
381 return {};
382 }
383 return {reinterpret_cast<const uint8_t*>(arr.data()), arr.size()};
384 }
385
386 // Support both "long long" and "long" on 64-bit systems
387
388 template <typename U>
389 requires(sizeof(U) == sizeof(jlong) && std::integral<U> &&
390 is_qualification_convertible_v<T, U>)
391 operator std::span<U, Size>() const
392 requires std::is_same_v<std::remove_cv_t<T>, jlong>
393 {
394 auto arr = array();
395 if (arr.empty()) {
396 return {};
397 }
398 return {reinterpret_cast<U*>(arr.data()), arr.size()};
399 }
400
401 private:
402 bool m_valid;
403 JNIEnv* m_env;
404 jarray_type m_jarr = nullptr;
405 size_t m_size;
406 std::remove_cv_t<T>* m_elements;
407};
408
409} // namespace detail
410
411template <typename T, size_t Extent = std::dynamic_extent>
413
414template <typename T, size_t Extent = std::dynamic_extent>
416
417//
418// Conversions from C++ to Java objects
419//
420
421/**
422 * Convert a UTF8 string into a jstring.
423 *
424 * @param env JRE environment.
425 * @param str String to convert.
426 */
427inline jstring MakeJString(JNIEnv* env, std::string_view str) {
429 convertUTF8ToUTF16String(str, chars);
430 return env->NewString(chars.begin(), chars.size());
431}
432
433// details for MakeJIntArray
434namespace detail {
435
436template <typename T>
438 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
439 if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
440 // Fast path (use SetIntArrayRegion).
441 jintArray jarr = env->NewIntArray(arr.size());
442 if (!jarr) {
443 return nullptr;
444 }
445 env->SetIntArrayRegion(jarr, 0, arr.size(),
446 reinterpret_cast<const jint*>(arr.data()));
447 return jarr;
448 } else {
449 // Slow path (get primitive array and set individual elements).
450 //
451 // This is used if the input type is not an integer of the same size (note
452 // signed/unsigned is ignored).
453 jintArray jarr = env->NewIntArray(arr.size());
454 if (!jarr) {
455 return nullptr;
456 }
457 jint* elements =
458 static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
459 if (!elements) {
460 return nullptr;
461 }
462 for (size_t i = 0; i < arr.size(); ++i) {
463 elements[i] = static_cast<jint>(arr[i]);
464 }
465 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
466 return jarr;
467 }
468 }
469};
470
471} // namespace detail
472
473/**
474 * Convert a span to a jintArray.
475 *
476 * @param env JRE environment.
477 * @param arr Span to convert.
478 */
479template <typename T>
480inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
481 return detail::ConvertIntArray<T>::ToJava(env, arr);
482}
483
484/**
485 * Convert a span to a jintArray.
486 *
487 * @param env JRE environment.
488 * @param arr Span to convert.
489 */
490template <typename T>
491inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
492 return detail::ConvertIntArray<T>::ToJava(env, arr);
493}
494
495/**
496 * Convert a SmallVector to a jintArray.
497 *
498 * This is required in addition to ArrayRef because template resolution occurs
499 * prior to implicit conversions.
500 *
501 * @param env JRE environment.
502 * @param arr SmallVector to convert.
503 */
504template <typename T>
505inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
506 return detail::ConvertIntArray<T>::ToJava(env, arr);
507}
508
509/**
510 * Convert a std::vector to a jintArray.
511 *
512 * This is required in addition to ArrayRef because template resolution occurs
513 * prior to implicit conversions.
514 *
515 * @param env JRE environment.
516 * @param arr SmallVector to convert.
517 */
518template <typename T>
519inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
520 return detail::ConvertIntArray<T>::ToJava(env, arr);
521}
522
523/**
524 * Convert a span into a jbyteArray.
525 *
526 * @param env JRE environment.
527 * @param str span to convert.
528 */
529inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
530 jbyteArray jarr = env->NewByteArray(str.size());
531 if (!jarr) {
532 return nullptr;
533 }
534 env->SetByteArrayRegion(jarr, 0, str.size(),
535 reinterpret_cast<const jbyte*>(str.data()));
536 return jarr;
537}
538
539/**
540 * Convert an array of integers into a jbooleanArray.
541 *
542 * @param env JRE environment.
543 * @param arr Array to convert.
544 */
545inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
546 jbooleanArray jarr = env->NewBooleanArray(arr.size());
547 if (!jarr) {
548 return nullptr;
549 }
550 jboolean* elements =
551 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
552 if (!elements) {
553 return nullptr;
554 }
555 for (size_t i = 0; i < arr.size(); ++i) {
556 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
557 }
558 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
559 return jarr;
560}
561
562/**
563 * Convert an array of booleans into a jbooleanArray.
564 *
565 * @param env JRE environment.
566 * @param arr Array to convert.
567 */
568inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
569 jbooleanArray jarr = env->NewBooleanArray(arr.size());
570 if (!jarr) {
571 return nullptr;
572 }
573 jboolean* elements =
574 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
575 if (!elements) {
576 return nullptr;
577 }
578 for (size_t i = 0; i < arr.size(); ++i) {
579 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
580 }
581 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
582 return jarr;
583}
584
585// Other MakeJ*Array conversions.
586
587#define WPI_JNI_MAKEJARRAY(T, F) \
588 inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
589 T##Array jarr = env->New##F##Array(arr.size()); \
590 if (!jarr) { \
591 return nullptr; \
592 } \
593 env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
594 return jarr; \
595 }
596
598WPI_JNI_MAKEJARRAY(jbyte, Byte)
599WPI_JNI_MAKEJARRAY(jshort, Short)
600WPI_JNI_MAKEJARRAY(jfloat, Float)
601WPI_JNI_MAKEJARRAY(jdouble, Double)
602
603#undef WPI_JNI_MAKEJARRAY
604
605template <class T>
606 requires(sizeof(typename T::value_type) == sizeof(jlong) &&
607 std::integral<typename T::value_type>)
608inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
609 jlongArray jarr = env->NewLongArray(arr.size());
610 if (!jarr) {
611 return nullptr;
612 }
613 env->SetLongArrayRegion(jarr, 0, arr.size(),
614 reinterpret_cast<const jlong*>(arr.data()));
615 return jarr;
616}
617
618/**
619 * Convert an array of std::string into a jarray of jstring.
620 *
621 * @param env JRE environment.
622 * @param arr Array to convert.
623 */
624inline jobjectArray MakeJStringArray(JNIEnv* env,
625 std::span<const std::string> arr) {
626 static JClass stringCls{env, "java/lang/String"};
627 if (!stringCls) {
628 return nullptr;
629 }
630 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
631 if (!jarr) {
632 return nullptr;
633 }
634 for (size_t i = 0; i < arr.size(); ++i) {
635 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
636 env->SetObjectArrayElement(jarr, i, elem.obj());
637 }
638 return jarr;
639}
640
641/**
642 * Convert an array of std::string into a jarray of jstring.
643 *
644 * @param env JRE environment.
645 * @param arr Array to convert.
646 */
647inline jobjectArray MakeJStringArray(JNIEnv* env,
648 std::span<std::string_view> arr) {
649 static JClass stringCls{env, "java/lang/String"};
650 if (!stringCls) {
651 return nullptr;
652 }
653 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
654 if (!jarr) {
655 return nullptr;
656 }
657 for (size_t i = 0; i < arr.size(); ++i) {
658 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
659 env->SetObjectArrayElement(jarr, i, elem.obj());
660 }
661 return jarr;
662}
663
664/**
665 * Generic callback thread implementation.
666 *
667 * JNI's AttachCurrentThread() creates a Java Thread object on every
668 * invocation, which is both time inefficient and causes issues with Eclipse
669 * (which tries to keep a thread list up-to-date and thus gets swamped).
670 *
671 * Instead, this class attaches just once. When a hardware notification
672 * occurs, a condition variable wakes up this thread and this thread actually
673 * makes the call into Java.
674 *
675 * The template parameter T is the message being passed to the callback, but
676 * also needs to provide the following functions:
677 * static JavaVM* GetJVM();
678 * static const char* GetName();
679 * void CallJava(JNIEnv *env, jobject func, jmethodID mid);
680 */
681template <typename T>
683 public:
684 void Main() override;
685
686 std::queue<T> m_queue;
687 jobject m_func = nullptr;
688 jmethodID m_mid;
689};
690
691template <typename T>
692class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
693 public:
694 JCallbackManager() { this->SetJoinAtExit(false); }
695 void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
696
697 template <typename... Args>
698 void Send(Args&&... args);
699};
700
701template <typename T>
702void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
703 auto thr = this->GetThread();
704 if (!thr) {
705 return;
706 }
707 // free global reference
708 if (thr->m_func) {
709 env->DeleteGlobalRef(thr->m_func);
710 }
711 // create global reference
712 thr->m_func = env->NewGlobalRef(func);
713 thr->m_mid = mid;
714}
715
716template <typename T>
717template <typename... Args>
718void JCallbackManager<T>::Send(Args&&... args) {
719 auto thr = this->GetThread();
720 if (!thr) {
721 return;
722 }
723 thr->m_queue.emplace(std::forward<Args>(args)...);
724 thr->m_cond.notify_one();
725}
726
727template <typename T>
729 JNIEnv* env;
730 JavaVMAttachArgs args;
731 args.version = JNI_VERSION_1_2;
732 args.name = const_cast<char*>(T::GetName());
733 args.group = nullptr;
734 jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
735 reinterpret_cast<void**>(&env), &args);
736 if (rs != JNI_OK) {
737 return;
738 }
739
740 std::unique_lock lock(m_mutex);
741 while (m_active) {
742 m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
743 if (!m_active) {
744 break;
745 }
746 while (!m_queue.empty()) {
747 if (!m_active) {
748 break;
749 }
750 auto item = std::move(m_queue.front());
751 m_queue.pop();
752 auto func = m_func;
753 auto mid = m_mid;
754 lock.unlock(); // don't hold mutex during callback execution
755 item.CallJava(env, func, mid);
756 if (env->ExceptionCheck()) {
757 env->ExceptionDescribe();
758 env->ExceptionClear();
759 }
760 lock.lock();
761 }
762 }
763
764 JavaVM* jvm = T::GetJVM();
765 if (jvm) {
766 jvm->DetachCurrentThread();
767 }
768}
769
770template <typename T>
772 public:
774 static JSingletonCallbackManager<T> instance;
775 return instance;
776 }
777};
778
779inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
780 // create a throwable
781 static JClass throwableCls(env, "java/lang/Throwable");
782 if (!throwableCls) {
783 return "";
784 }
785 static jmethodID constructorId = nullptr;
786 if (!constructorId) {
787 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
788 }
789 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
790
791 // retrieve information from the exception.
792 // get method id
793 // getStackTrace returns an array of StackTraceElement
794 static jmethodID getStackTraceId = nullptr;
795 if (!getStackTraceId) {
796 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
797 "()[Ljava/lang/StackTraceElement;");
798 }
799
800 // call getStackTrace
801 JLocal<jobjectArray> stackTrace(
802 env, static_cast<jobjectArray>(
803 env->CallObjectMethod(throwable, getStackTraceId)));
804
805 if (!stackTrace) {
806 return "";
807 }
808
809 // get length of the array
810 jsize stackTraceLength = env->GetArrayLength(stackTrace);
811
812 // get toString methodId of StackTraceElement class
813 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
814 if (!stackTraceElementCls) {
815 return "";
816 }
817 static jmethodID toStringId = nullptr;
818 if (!toStringId) {
819 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
820 "()Ljava/lang/String;");
821 }
822
823 bool foundFirst = false;
824 std::string buf;
825 raw_string_ostream oss(buf);
826 for (jsize i = 0; i < stackTraceLength; i++) {
827 // add the result of toString method of each element in the result
828 JLocal<jobject> curStackTraceElement(
829 env, env->GetObjectArrayElement(stackTrace, i));
830
831 // call to string on the object
832 JLocal<jstring> stackElementString(
833 env, static_cast<jstring>(
834 env->CallObjectMethod(curStackTraceElement, toStringId)));
835
836 if (!stackElementString) {
837 return "";
838 }
839
840 // add a line to res
841 JStringRef elem(env, stackElementString);
842 if (!foundFirst) {
843 if (wpi::starts_with(elem, skipPrefix)) {
844 continue;
845 }
846 foundFirst = true;
847 }
848 oss << "\tat " << elem << '\n';
849 }
850
851 return oss.str();
852}
853
854inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
855 std::string_view excludeFuncPrefix) {
856 // create a throwable
857 static JClass throwableCls(env, "java/lang/Throwable");
858 if (!throwableCls) {
859 return "";
860 }
861 static jmethodID constructorId = nullptr;
862 if (!constructorId) {
863 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
864 }
865 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
866
867 // retrieve information from the exception.
868 // get method id
869 // getStackTrace returns an array of StackTraceElement
870 static jmethodID getStackTraceId = nullptr;
871 if (!getStackTraceId) {
872 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
873 "()[Ljava/lang/StackTraceElement;");
874 }
875
876 // call getStackTrace
877 JLocal<jobjectArray> stackTrace(
878 env, static_cast<jobjectArray>(
879 env->CallObjectMethod(throwable, getStackTraceId)));
880
881 if (!stackTrace) {
882 return "";
883 }
884
885 // get length of the array
886 jsize stackTraceLength = env->GetArrayLength(stackTrace);
887
888 // get toString methodId of StackTraceElement class
889 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
890 if (!stackTraceElementCls) {
891 return "";
892 }
893 static jmethodID toStringId = nullptr;
894 if (!toStringId) {
895 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
896 "()Ljava/lang/String;");
897 }
898
899 bool haveLoc = false;
900 std::string buf;
901 raw_string_ostream oss(buf);
902 for (jsize i = 0; i < stackTraceLength; i++) {
903 // add the result of toString method of each element in the result
904 JLocal<jobject> curStackTraceElement(
905 env, env->GetObjectArrayElement(stackTrace, i));
906
907 // call to string on the object
908 JLocal<jstring> stackElementString(
909 env, static_cast<jstring>(
910 env->CallObjectMethod(curStackTraceElement, toStringId)));
911
912 if (!stackElementString) {
913 return "";
914 }
915
916 // add a line to res
917 JStringRef elem(env, stackElementString);
918 oss << elem << '\n';
919
920 if (func) {
921 // func is caller of immediate caller (if there was one)
922 // or, if we see it, the first user function
923 if (i == 1) {
924 *func = elem.str();
925 } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
926 !wpi::starts_with(elem, excludeFuncPrefix)) {
927 *func = elem.str();
928 haveLoc = true;
929 }
930 }
931 }
932
933 return oss.str();
934}
935
936/**
937 * Finds an exception class and keep it as a global reference.
938 *
939 * Similar to JClass, but provides Throw methods. Use with caution, as the
940 * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
941 * with doing so.
942 */
943class JException : public JClass {
944 public:
945 JException() = default;
946 JException(JNIEnv* env, const char* name) : JClass(env, name) {
947 if (m_cls) {
948 m_constructor =
949 env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
950 }
951 }
952
953 void Throw(JNIEnv* env, jstring msg) {
954 jobject exception = env->NewObject(m_cls, m_constructor, msg);
955 env->Throw(static_cast<jthrowable>(exception));
956 }
957
958 void Throw(JNIEnv* env, std::string_view msg) {
959 Throw(env, MakeJString(env, msg));
960 }
961
962 explicit operator bool() const { return m_constructor; }
963
964 private:
965 jmethodID m_constructor = nullptr;
966};
967
969 const char* name;
971};
972
973} // namespace wpi::java
974
975#endif // WPIUTIL_WPI_JNI_UTIL_H_
This file defines the SmallString class.
This file defines the SmallVector class.
you may not use this file except in compliance with the License You may obtain a copy of the License at software distributed under the License is distributed on an AS IS WITHOUT WARRANTIES OR CONDITIONS OF ANY either express or implied See the License for the specific language governing permissions and limitations under the License LLVM Exceptions to the Apache License As an exception
Definition: ThirdPartyNotices.txt:289
Definition: SafeThread.h:33
Definition: SafeThread.h:124
std::string_view str() const
Explicit conversion to std::string_view.
Definition: SmallString.h:181
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
Definition: SmallVector.h:1202
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
Definition: SmallVector.h:579
void pop_back()
Definition: SmallVector.h:430
void push_back(const T &Elt)
Definition: SmallVector.h:418
size_t size() const
Definition: SmallVector.h:98
pointer data()
Return a pointer to the vector's buffer, even if empty().
Definition: SmallVector.h:291
iterator begin()
Definition: SmallVector.h:272
void SetJoinAtExit(bool joinAtExit)
Definition: SafeThread.h:106
Definition: jni_util.h:692
JCallbackManager()
Definition: jni_util.h:694
void SetFunc(JNIEnv *env, jobject func, jmethodID mid)
Definition: jni_util.h:702
void Send(Args &&... args)
Definition: jni_util.h:718
Generic callback thread implementation.
Definition: jni_util.h:682
jmethodID m_mid
Definition: jni_util.h:688
jobject m_func
Definition: jni_util.h:687
std::queue< T > m_queue
Definition: jni_util.h:686
void Main() override
Definition: jni_util.h:728
Finds a class and keeps it as a global reference.
Definition: jni_util.h:52
JClass(JNIEnv *env, const char *name)
Definition: jni_util.h:56
jclass m_cls
Definition: jni_util.h:77
void free(JNIEnv *env)
Definition: jni_util.h:65
Finds an exception class and keep it as a global reference.
Definition: jni_util.h:943
void Throw(JNIEnv *env, std::string_view msg)
Definition: jni_util.h:958
JException(JNIEnv *env, const char *name)
Definition: jni_util.h:946
void Throw(JNIEnv *env, jstring msg)
Definition: jni_util.h:953
Definition: jni_util.h:86
JGlobal(JNIEnv *env, T obj)
Definition: jni_util.h:90
void free(JNIEnv *env)
Definition: jni_util.h:94
T m_cls
Definition: jni_util.h:106
Container class for cleaning up Java local references.
Definition: jni_util.h:115
JLocal & operator=(const JLocal &)=delete
~JLocal()
Definition: jni_util.h:129
JLocal(JLocal &&oth)
Definition: jni_util.h:119
JLocal & operator=(JLocal &&oth)
Definition: jni_util.h:123
T obj()
Definition: jni_util.h:135
JLocal(JNIEnv *env, T obj)
Definition: jni_util.h:117
JLocal(const JLocal &)=delete
Definition: jni_util.h:771
static JSingletonCallbackManager< T > & GetInstance()
Definition: jni_util.h:773
Java string (jstring) reference.
Definition: jni_util.h:152
std::string_view str() const
Definition: jni_util.h:172
const char * c_str() const
Definition: jni_util.h:173
size_t size() const
Definition: jni_util.h:174
JStringRef(JNIEnv *env, jstring str)
Definition: jni_util.h:154
Helper class for working with JNI arrays.
Definition: jni_util.h:247
size_t m_size
Definition: jni_util.h:405
JSpanBase(const JSpanBase &)=delete
T & operator[](size_t i)
Definition: jni_util.h:352
JSpanBase(JNIEnv *env, jarray_type jarr)
Definition: jni_util.h:310
bool is_valid() const
Definition: jni_util.h:342
T * end() const
Definition: jni_util.h:340
JSpanBase & operator=(JSpanBase &&other)
Definition: jni_util.h:265
JSpanBase(JNIEnv *env, jarray_type jarr, size_t size)
Definition: jni_util.h:291
std::span< copy_cv_t< T, uint8_t >, Size > uarray() const
Definition: jni_util.h:376
std::remove_cv_t< T > * m_elements
Definition: jni_util.h:406
operator std::span< U, Size >() const std JNIEnv * m_env
Definition: jni_util.h:391
T * begin() const
Definition: jni_util.h:338
const T & operator[](size_t i) const
Definition: jni_util.h:350
size_t size() const
Definition: jni_util.h:348
T * data() const
Definition: jni_util.h:346
jarray_type m_jarr
Definition: jni_util.h:404
JSpanBase(JNIEnv *env, jobject bb, size_t size)
Definition: jni_util.h:277
std::string_view str() const
Definition: jni_util.h:366
JSpanBase(JSpanBase &&other)
Definition: jni_util.h:255
std::span< T, Size > array() const
Definition: jni_util.h:326
JSpanBase & operator=(const JSpanBase &)=delete
~JSpanBase()
Definition: jni_util.h:313
A raw_ostream that writes to an std::string.
Definition: raw_ostream.h:572
std::string & str()
Returns the string's reference.
Definition: raw_ostream.h:590
Definition: jni_util.h:209
basic_string_view< char > string_view
Definition: core.h:501
void print(wpi::raw_ostream &os, const S &format_str, Args &&... args)
Prints formatted data to the stream os.
Definition: raw_ostream.h:25
#define WPI_JNI_MAKEJARRAY(T, F)
Definition: jni_util.h:587
#define WPI_JNI_ARRAYHELPER(T, F)
Definition: jni_util.h:185
detail namespace with internal helper functions
Definition: xchar.h:20
Definition: array.h:89
constexpr bool is_qualification_convertible_v
Definition: jni_util.h:227
typename copy_cv< CvSrc, Dest >::type copy_cv_t
Definition: jni_util.h:224
Java Native Interface (JNI) utility functions.
Definition: jni_util.h:29
jlongArray MakeJLongArray(JNIEnv *env, const T &arr)
Definition: jni_util.h:608
jintArray MakeJIntArray(JNIEnv *env, std::span< const T > arr)
Convert a span to a jintArray.
Definition: jni_util.h:480
jstring MakeJString(JNIEnv *env, std::string_view str)
Convert a UTF8 string into a jstring.
Definition: jni_util.h:427
std::string GetJavaStackTrace(JNIEnv *env, std::string *func=nullptr, std::string_view excludeFuncPrefix={})
Gets a Java stack trace.
Definition: jni_util.h:854
jbooleanArray MakeJBooleanArray(JNIEnv *env, std::span< const int > arr)
Convert an array of integers into a jbooleanArray.
Definition: jni_util.h:545
jobjectArray MakeJStringArray(JNIEnv *env, std::span< const std::string > arr)
Convert an array of std::string into a jarray of jstring.
Definition: jni_util.h:624
jbyteArray MakeJByteArray(JNIEnv *env, std::span< const uint8_t > str)
Convert a span into a jbyteArray.
Definition: jni_util.h:529
bool convertUTF8ToUTF16String(std::string_view SrcUTF8, SmallVectorImpl< UTF16 > &DstUTF16)
Converts a UTF-8 string into a UTF-16 string with native endianness.
bool convertUTF16ToUTF8String(std::span< const char > SrcBytes, SmallVectorImpl< char > &Out)
Converts a stream of raw bytes assumed to be UTF16 into a UTF8 std::string.
constexpr bool starts_with(std::string_view str, std::string_view prefix) noexcept
Checks if str starts with the given prefix.
Definition: StringExtras.h:235
Definition: jni_util.h:80
const char * name
Definition: jni_util.h:81
JClass * cls
Definition: jni_util.h:82
Definition: jni_util.h:968
JException * cls
Definition: jni_util.h:970
const char * name
Definition: jni_util.h:969
Definition: jni_util.h:183
Definition: jni_util.h:437
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition: jni_util.h:438
Definition: jni_util.h:213
U2 type
Definition: jni_util.h:220
bool Boolean
Definition: ConvertUTF.h:130