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