WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
jni_util.hpp
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#pragma once
6
7#include <jni.h>
8
9#include <concepts>
10#include <queue>
11#include <span>
12#include <string>
13#include <string_view>
14#include <utility>
15#include <vector>
16
20#include "wpi/util/SmallVector.hpp"
21#include "wpi/util/StringExtras.hpp"
22#include "wpi/util/mutex.hpp"
23#include "wpi/util/print.hpp"
24#include "wpi/util/raw_ostream.hpp"
25#include "wpi/util/string.hpp"
26
27/** Java Native Interface (JNI) utility functions */
28namespace wpi::util::java {
29
30/**
31 * Gets a Java stack trace.
32 *
33 * Also provides the last function in the stack trace not starting with
34 * excludeFuncPrefix (useful for e.g. finding the first user call to a series of
35 * library functions).
36 *
37 * @param env JRE environment.
38 * @param func Storage for last function in the stack trace not starting with
39 * excludeFuncPrefix.
40 * @param excludeFuncPrefix Prefix for functions to ignore in stack trace.
41 */
42std::string GetJavaStackTrace(JNIEnv* env, std::string* func = nullptr,
43 std::string_view excludeFuncPrefix = {});
44
45/**
46 * Finds a class and keeps it as a global reference.
47 *
48 * Use with caution, as the destructor does NOT call DeleteGlobalRef due to
49 * potential shutdown issues with doing so.
50 */
51class JClass {
52 public:
53 JClass() = default;
54
55 JClass(JNIEnv* env, const char* name) {
56 jclass local = env->FindClass(name);
57 if (!local) {
58 return;
59 }
60 m_cls = static_cast<jclass>(env->NewGlobalRef(local));
61 env->DeleteLocalRef(local);
62 }
63
64 void free(JNIEnv* env) {
65 if (m_cls) {
66 env->DeleteGlobalRef(m_cls);
67 }
68 m_cls = nullptr;
69 }
70
71 explicit operator bool() const { return m_cls; }
72
73 operator jclass() const { return m_cls; }
74
75 protected:
76 jclass m_cls = nullptr;
77};
78
79struct JClassInit {
80 const char* name;
82};
83
84template <typename T>
85class JGlobal {
86 public:
87 JGlobal() = default;
88
89 JGlobal(JNIEnv* env, T obj) {
90 m_cls = static_cast<T>(env->NewGlobalRef(obj));
91 }
92
93 void free(JNIEnv* env) {
94 if (m_cls) {
95 env->DeleteGlobalRef(m_cls);
96 }
97 m_cls = nullptr;
98 }
99
100 explicit operator bool() const { return m_cls; }
101
102 operator T() const { return m_cls; } // NOLINT
103
104 protected:
105 T m_cls = nullptr;
106};
107
108/**
109 * Container class for cleaning up Java local references.
110 *
111 * The destructor calls DeleteLocalRef.
112 */
113template <typename T>
114class JLocal {
115 public:
116 JLocal(JNIEnv* env, T obj) : m_env(env), m_obj(obj) {}
117 JLocal(const JLocal&) = delete;
118 JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
119 oth.m_obj = nullptr;
120 }
121 JLocal& operator=(const JLocal&) = delete;
123 m_env = oth.m_env;
124 m_obj = oth.m_obj;
125 oth.m_obj = nullptr;
126 return *this;
127 }
129 if (m_obj) {
130 m_env->DeleteLocalRef(m_obj);
131 }
132 }
133 operator T() { return m_obj; } // NOLINT
134 T obj() { return m_obj; }
135
136 private:
137 JNIEnv* m_env;
138 T m_obj;
139};
140
141//
142// Conversions from Java objects to C++
143//
144
145/**
146 * Java string (jstring) reference.
147 *
148 * The string is provided as UTF8. This is not actually a reference, as it makes
149 * a copy of the string characters, but it's named this way for consistency.
150 */
152 public:
153 JStringRef(JNIEnv* env, jstring str) {
154 if (str) {
155 jsize size = env->GetStringLength(str);
156 const jchar* chars = env->GetStringCritical(str, nullptr);
157 if (chars) {
158 convertUTF16ToUTF8String(std::span<const jchar>(chars, size), m_str);
159 env->ReleaseStringCritical(str, chars);
160 }
161 } else {
162 wpi::util::print(stderr, "JStringRef was passed a null pointer at\n",
163 GetJavaStackTrace(env));
164 }
165 // Ensure str is null-terminated.
166 m_str.push_back('\0');
167 m_str.pop_back();
168 }
169
170 operator std::string_view() const { return m_str.str(); } // NOLINT
171 std::string_view str() const { return m_str.str(); }
172 const char* c_str() const { return m_str.data(); }
173 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>
248 using ArrHelper = ArrayHelper<std::remove_cv_t<T>>;
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 wpi::util::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 wpi::util::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 // NOLINTNEXTLINE(google-explicit-constructor)
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
367 std::string_view str() const
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> &&
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 // FIXME doxygen gives error parsing initializer list
403 //! @cond Doxygen_Suppress
404 private:
405 bool m_valid;
406 JNIEnv* m_env;
407 jarray_type m_jarr = nullptr;
408 size_t m_size;
409 std::remove_cv_t<T>* m_elements;
410 //! @endcond
411};
412
413} // namespace detail
414
415template <typename T, size_t Extent = std::dynamic_extent>
417
418template <typename T, size_t Extent = std::dynamic_extent>
420
421//
422// Conversions from C++ to Java objects
423//
424
425/**
426 * Convert a UTF8 string into a jstring.
427 *
428 * @param env JRE environment.
429 * @param str String to convert.
430 */
431inline jstring MakeJString(JNIEnv* env, std::string_view str) {
432 SmallVector<UTF16, 128> chars;
433 convertUTF8ToUTF16String(str, chars);
434 return env->NewString(chars.begin(), chars.size());
435}
436
437// details for MakeJIntArray
438namespace detail {
439
440template <typename T>
442 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
443 if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
444 // Fast path (use SetIntArrayRegion).
445 jintArray jarr = env->NewIntArray(arr.size());
446 if (!jarr) {
447 return nullptr;
448 }
449 env->SetIntArrayRegion(jarr, 0, arr.size(),
450 reinterpret_cast<const jint*>(arr.data()));
451 return jarr;
452 } else {
453 // Slow path (get primitive array and set individual elements).
454 //
455 // This is used if the input type is not an integer of the same size (note
456 // signed/unsigned is ignored).
457 jintArray jarr = env->NewIntArray(arr.size());
458 if (!jarr) {
459 return nullptr;
460 }
461 jint* elements =
462 static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
463 if (!elements) {
464 return nullptr;
465 }
466 for (size_t i = 0; i < arr.size(); ++i) {
467 elements[i] = static_cast<jint>(arr[i]);
468 }
469 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
470 return jarr;
471 }
472 }
473};
474
475} // namespace detail
476
477/**
478 * Convert a span to a jintArray.
479 *
480 * @param env JRE environment.
481 * @param arr Span to convert.
482 */
483template <typename T>
484inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
485 return detail::ConvertIntArray<T>::ToJava(env, arr);
486}
487
488/**
489 * Convert a span to a jintArray.
490 *
491 * @param env JRE environment.
492 * @param arr Span to convert.
493 */
494template <typename T>
495inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
496 return detail::ConvertIntArray<T>::ToJava(env, arr);
497}
498
499/**
500 * Convert a SmallVector to a jintArray.
501 *
502 * This is required in addition to ArrayRef because template resolution occurs
503 * prior to implicit conversions.
504 *
505 * @param env JRE environment.
506 * @param arr SmallVector to convert.
507 */
508template <typename T>
509inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
510 return detail::ConvertIntArray<T>::ToJava(env, arr);
511}
512
513/**
514 * Convert a std::vector to a jintArray.
515 *
516 * This is required in addition to ArrayRef because template resolution occurs
517 * prior to implicit conversions.
518 *
519 * @param env JRE environment.
520 * @param arr SmallVector to convert.
521 */
522template <typename T>
523inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
524 return detail::ConvertIntArray<T>::ToJava(env, arr);
525}
526
527/**
528 * Convert a span into a jbyteArray.
529 *
530 * @param env JRE environment.
531 * @param str span to convert.
532 */
533inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
534 jbyteArray jarr = env->NewByteArray(str.size());
535 if (!jarr) {
536 return nullptr;
537 }
538 env->SetByteArrayRegion(jarr, 0, str.size(),
539 reinterpret_cast<const jbyte*>(str.data()));
540 return jarr;
541}
542
543/**
544 * Convert an array of integers into a jbooleanArray.
545 *
546 * @param env JRE environment.
547 * @param arr Array to convert.
548 */
549inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
550 jbooleanArray jarr = env->NewBooleanArray(arr.size());
551 if (!jarr) {
552 return nullptr;
553 }
554 jboolean* elements =
555 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
556 if (!elements) {
557 return nullptr;
558 }
559 for (size_t i = 0; i < arr.size(); ++i) {
560 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
561 }
562 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
563 return jarr;
564}
565
566/**
567 * Convert an array of booleans into a jbooleanArray.
568 *
569 * @param env JRE environment.
570 * @param arr Array to convert.
571 */
572inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
573 jbooleanArray jarr = env->NewBooleanArray(arr.size());
574 if (!jarr) {
575 return nullptr;
576 }
577 jboolean* elements =
578 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
579 if (!elements) {
580 return nullptr;
581 }
582 for (size_t i = 0; i < arr.size(); ++i) {
583 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
584 }
585 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
586 return jarr;
587}
588
589// Other MakeJ*Array conversions.
590
591#define WPI_JNI_MAKEJARRAY(T, F) \
592 inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
593 T##Array jarr = env->New##F##Array(arr.size()); \
594 if (!jarr) { \
595 return nullptr; \
596 } \
597 env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
598 return jarr; \
599 }
600
602WPI_JNI_MAKEJARRAY(jbyte, Byte)
603WPI_JNI_MAKEJARRAY(jshort, Short)
604WPI_JNI_MAKEJARRAY(jint, Int)
605WPI_JNI_MAKEJARRAY(jlong, Long)
606WPI_JNI_MAKEJARRAY(jfloat, Float)
607WPI_JNI_MAKEJARRAY(jdouble, Double)
608
609#undef WPI_JNI_MAKEJARRAY
610
611template <class T>
612 requires(sizeof(typename T::value_type) == sizeof(jlong) &&
613 std::integral<typename T::value_type>)
614inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
615 jlongArray jarr = env->NewLongArray(arr.size());
616 if (!jarr) {
617 return nullptr;
618 }
619 env->SetLongArrayRegion(jarr, 0, arr.size(),
620 reinterpret_cast<const jlong*>(arr.data()));
621 return jarr;
622}
623
624/**
625 * Convert an array of std::string into a jarray of jstring.
626 *
627 * @param env JRE environment.
628 * @param arr Array to convert.
629 */
630inline jobjectArray MakeJStringArray(JNIEnv* env,
631 std::span<const std::string> arr) {
632 static JClass stringCls{env, "java/lang/String"};
633 if (!stringCls) {
634 return nullptr;
635 }
636 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
637 if (!jarr) {
638 return nullptr;
639 }
640 for (size_t i = 0; i < arr.size(); ++i) {
641 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
642 env->SetObjectArrayElement(jarr, i, elem.obj());
643 }
644 return jarr;
645}
646
647/**
648 * Convert an array of std::string into a jarray of jstring.
649 *
650 * @param env JRE environment.
651 * @param arr Array to convert.
652 */
653inline jobjectArray MakeJStringArray(JNIEnv* env,
654 std::span<std::string_view> arr) {
655 static JClass stringCls{env, "java/lang/String"};
656 if (!stringCls) {
657 return nullptr;
658 }
659 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
660 if (!jarr) {
661 return nullptr;
662 }
663 for (size_t i = 0; i < arr.size(); ++i) {
664 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
665 env->SetObjectArrayElement(jarr, i, elem.obj());
666 }
667 return jarr;
668}
669
670/**
671 * Generic callback thread implementation.
672 *
673 * JNI's AttachCurrentThread() creates a Java Thread object on every
674 * invocation, which is both time inefficient and causes issues with Eclipse
675 * (which tries to keep a thread list up-to-date and thus gets swamped).
676 *
677 * Instead, this class attaches just once. When a hardware notification
678 * occurs, a condition variable wakes up this thread and this thread actually
679 * makes the call into Java.
680 *
681 * The template parameter T is the message being passed to the callback, but
682 * also needs to provide the following functions:
683 * static JavaVM* GetJVM();
684 * static const char* GetName();
685 * void CallJava(JNIEnv *env, jobject func, jmethodID mid);
686 */
687template <typename T>
689 public:
690 void Main() override;
691
692 std::queue<T> m_queue;
693 jobject m_func = nullptr;
694 jmethodID m_mid;
695};
696
697template <typename T>
698class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
699 public:
700 JCallbackManager() { this->SetJoinAtExit(false); }
701 void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
702
703 template <typename... Args>
704 void Send(Args&&... args);
705};
706
707template <typename T>
708void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
709 auto thr = this->GetThread();
710 if (!thr) {
711 return;
712 }
713 // free global reference
714 if (thr->m_func) {
715 env->DeleteGlobalRef(thr->m_func);
716 }
717 // create global reference
718 thr->m_func = env->NewGlobalRef(func);
719 thr->m_mid = mid;
720}
721
722template <typename T>
723template <typename... Args>
724void JCallbackManager<T>::Send(Args&&... args) {
725 auto thr = this->GetThread();
726 if (!thr) {
727 return;
728 }
729 thr->m_queue.emplace(std::forward<Args>(args)...);
730 thr->m_cond.notify_one();
731}
732
733template <typename T>
735 JNIEnv* env;
736 JavaVMAttachArgs args;
737 args.version = JNI_VERSION_1_2;
738 args.name = const_cast<char*>(T::GetName());
739 args.group = nullptr;
740 jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
741 reinterpret_cast<void**>(&env), &args);
742 if (rs != JNI_OK) {
743 return;
744 }
745
746 std::unique_lock lock(m_mutex);
747 while (m_active) {
748 m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
749 if (!m_active) {
750 break;
751 }
752 while (!m_queue.empty()) {
753 if (!m_active) {
754 break;
755 }
756 auto item = std::move(m_queue.front());
757 m_queue.pop();
758 auto func = m_func;
759 auto mid = m_mid;
760 lock.unlock(); // don't hold mutex during callback execution
761 item.CallJava(env, func, mid);
762 if (env->ExceptionCheck()) {
763 env->ExceptionDescribe();
764 env->ExceptionClear();
765 }
766 lock.lock();
767 }
768 }
769
770 JavaVM* jvm = T::GetJVM();
771 if (jvm) {
772 jvm->DetachCurrentThread();
773 }
774}
775
776template <typename T>
778 public:
780 static JSingletonCallbackManager<T> instance;
781 return instance;
782 }
783};
784
785inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
786 // create a throwable
787 static JClass throwableCls(env, "java/lang/Throwable");
788 if (!throwableCls) {
789 return "";
790 }
791 static jmethodID constructorId = nullptr;
792 if (!constructorId) {
793 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
794 }
795 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
796
797 // retrieve information from the exception.
798 // get method id
799 // getStackTrace returns an array of StackTraceElement
800 static jmethodID getStackTraceId = nullptr;
801 if (!getStackTraceId) {
802 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
803 "()[Ljava/lang/StackTraceElement;");
804 }
805
806 // call getStackTrace
807 JLocal<jobjectArray> stackTrace(
808 env, static_cast<jobjectArray>(
809 env->CallObjectMethod(throwable, getStackTraceId)));
810
811 if (!stackTrace) {
812 return "";
813 }
814
815 // get length of the array
816 jsize stackTraceLength = env->GetArrayLength(stackTrace);
817
818 // get toString methodId of StackTraceElement class
819 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
820 if (!stackTraceElementCls) {
821 return "";
822 }
823 static jmethodID toStringId = nullptr;
824 if (!toStringId) {
825 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
826 "()Ljava/lang/String;");
827 }
828
829 bool foundFirst = false;
830 std::string buf;
831 raw_string_ostream oss(buf);
832 for (jsize i = 0; i < stackTraceLength; i++) {
833 // add the result of toString method of each element in the result
834 JLocal<jobject> curStackTraceElement(
835 env, env->GetObjectArrayElement(stackTrace, i));
836
837 // call to string on the object
838 JLocal<jstring> stackElementString(
839 env, static_cast<jstring>(
840 env->CallObjectMethod(curStackTraceElement, toStringId)));
841
842 if (!stackElementString) {
843 return "";
844 }
845
846 // add a line to res
847 JStringRef elem(env, stackElementString);
848 if (!foundFirst) {
849 if (wpi::util::starts_with(elem, skipPrefix)) {
850 continue;
851 }
852 foundFirst = true;
853 }
854 oss << "\tat " << elem << '\n';
855 }
856
857 return oss.str();
858}
859
860inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
861 std::string_view excludeFuncPrefix) {
862 // create a throwable
863 static JClass throwableCls(env, "java/lang/Throwable");
864 if (!throwableCls) {
865 return "";
866 }
867 static jmethodID constructorId = nullptr;
868 if (!constructorId) {
869 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
870 }
871 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
872
873 // retrieve information from the exception.
874 // get method id
875 // getStackTrace returns an array of StackTraceElement
876 static jmethodID getStackTraceId = nullptr;
877 if (!getStackTraceId) {
878 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
879 "()[Ljava/lang/StackTraceElement;");
880 }
881
882 // call getStackTrace
883 JLocal<jobjectArray> stackTrace(
884 env, static_cast<jobjectArray>(
885 env->CallObjectMethod(throwable, getStackTraceId)));
886
887 if (!stackTrace) {
888 return "";
889 }
890
891 // get length of the array
892 jsize stackTraceLength = env->GetArrayLength(stackTrace);
893
894 // get toString methodId of StackTraceElement class
895 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
896 if (!stackTraceElementCls) {
897 return "";
898 }
899 static jmethodID toStringId = nullptr;
900 if (!toStringId) {
901 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
902 "()Ljava/lang/String;");
903 }
904
905 bool haveLoc = false;
906 std::string buf;
907 raw_string_ostream oss(buf);
908 for (jsize i = 0; i < stackTraceLength; i++) {
909 // add the result of toString method of each element in the result
910 JLocal<jobject> curStackTraceElement(
911 env, env->GetObjectArrayElement(stackTrace, i));
912
913 // call to string on the object
914 JLocal<jstring> stackElementString(
915 env, static_cast<jstring>(
916 env->CallObjectMethod(curStackTraceElement, toStringId)));
917
918 if (!stackElementString) {
919 return "";
920 }
921
922 // add a line to res
923 JStringRef elem(env, stackElementString);
924 oss << elem << '\n';
925
926 if (func) {
927 // func is caller of immediate caller (if there was one)
928 // or, if we see it, the first user function
929 if (i == 1) {
930 *func = elem.str();
931 } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
932 !wpi::util::starts_with(elem, excludeFuncPrefix)) {
933 *func = elem.str();
934 haveLoc = true;
935 }
936 }
937 }
938
939 return oss.str();
940}
941
942/**
943 * Finds an exception class and keep it as a global reference.
944 *
945 * Similar to JClass, but provides Throw methods. Use with caution, as the
946 * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
947 * with doing so.
948 */
949class JException : public JClass {
950 public:
951 JException() = default;
952 JException(JNIEnv* env, const char* name) : JClass(env, name) {
953 if (m_cls) {
954 m_constructor =
955 env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
956 }
957 }
958
959 void Throw(JNIEnv* env, jstring msg) {
960 jobject exception = env->NewObject(m_cls, m_constructor, msg);
961 env->Throw(static_cast<jthrowable>(exception));
962 }
963
964 void Throw(JNIEnv* env, std::string_view msg) {
965 Throw(env, MakeJString(env, msg));
966 }
967
968 explicit operator bool() const { return m_constructor; }
969
970 private:
971 jmethodID m_constructor = nullptr;
972};
973
975 const char* name;
977};
978
979} // namespace wpi::util::java
This file defines the SmallString 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:301
@ name
Definition base.h:690
wpi::util::mutex m_mutex
Definition SafeThread.hpp:27
std::atomic_bool m_active
Definition SafeThread.hpp:28
Definition SafeThread.hpp:32
wpi::util::condition_variable m_cond
Definition SafeThread.hpp:36
Definition SafeThread.hpp:123
Proxy GetThread() const
Definition SafeThread.hpp:132
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.hpp:27
Definition BooleanTopic.hpp:24
void SetJoinAtExit(bool joinAtExit)
Definition SafeThread.hpp:105
void Send(Args &&... args)
Definition jni_util.hpp:724
void SetFunc(JNIEnv *env, jobject func, jmethodID mid)
Definition jni_util.hpp:708
JCallbackManager()
Definition jni_util.hpp:700
Generic callback thread implementation.
Definition jni_util.hpp:688
void Main() override
Definition jni_util.hpp:734
std::queue< T > m_queue
Definition jni_util.hpp:692
jmethodID m_mid
Definition jni_util.hpp:694
jobject m_func
Definition jni_util.hpp:693
Finds a class and keeps it as a global reference.
Definition jni_util.hpp:51
void free(JNIEnv *env)
Definition jni_util.hpp:64
jclass m_cls
Definition jni_util.hpp:76
JClass(JNIEnv *env, const char *name)
Definition jni_util.hpp:55
Finds an exception class and keep it as a global reference.
Definition jni_util.hpp:949
JException(JNIEnv *env, const char *name)
Definition jni_util.hpp:952
void Throw(JNIEnv *env, std::string_view msg)
Definition jni_util.hpp:964
void Throw(JNIEnv *env, jstring msg)
Definition jni_util.hpp:959
T m_cls
Definition jni_util.hpp:105
JGlobal(JNIEnv *env, T obj)
Definition jni_util.hpp:89
void free(JNIEnv *env)
Definition jni_util.hpp:93
Container class for cleaning up Java local references.
Definition jni_util.hpp:114
JLocal & operator=(const JLocal &)=delete
JLocal(JNIEnv *env, T obj)
Definition jni_util.hpp:116
~JLocal()
Definition jni_util.hpp:128
JLocal(JLocal &&oth)
Definition jni_util.hpp:118
JLocal(const JLocal &)=delete
T obj()
Definition jni_util.hpp:134
JLocal & operator=(JLocal &&oth)
Definition jni_util.hpp:122
static JSingletonCallbackManager< T > & GetInstance()
Definition jni_util.hpp:779
Java string (jstring) reference.
Definition jni_util.hpp:151
JStringRef(JNIEnv *env, jstring str)
Definition jni_util.hpp:153
std::string_view str() const
Definition jni_util.hpp:171
WPI_String wpi_str() const
Definition jni_util.hpp:174
size_t size() const
Definition jni_util.hpp:173
const char * c_str() const
Definition jni_util.hpp:172
Helper class for working with JNI arrays.
Definition jni_util.hpp:247
JSpanBase & operator=(JSpanBase &&other)
Definition jni_util.hpp:265
const T & operator[](size_t i) const
Definition jni_util.hpp:351
JSpanBase(const JSpanBase &)=delete
T * data() const
Definition jni_util.hpp:347
std::span< T, Size > array() const
Definition jni_util.hpp:327
std::string_view str() const
Definition jni_util.hpp:367
T * end() const
Definition jni_util.hpp:341
JSpanBase(JNIEnv *env, jobject bb, size_t size)
Definition jni_util.hpp:277
std::span< copy_cv_t< T, uint8_t >, Size > uarray() const
Definition jni_util.hpp:377
~JSpanBase()
Definition jni_util.hpp:313
bool is_valid() const
Definition jni_util.hpp:343
JSpanBase(JSpanBase &&other)
Definition jni_util.hpp:255
T & operator[](size_t i)
Definition jni_util.hpp:353
JSpanBase(JNIEnv *env, jarray_type jarr)
Definition jni_util.hpp:310
T * begin() const
Definition jni_util.hpp:339
JSpanBase & operator=(const JSpanBase &)=delete
JSpanBase(JNIEnv *env, jarray_type jarr, size_t size)
Definition jni_util.hpp:291
size_t size() const
Definition jni_util.hpp:349
Definition jni_util.hpp:209
#define WPI_JNI_MAKEJARRAY(T, F)
Definition jni_util.hpp:591
#define WPI_JNI_ARRAYHELPER(T, F)
Definition jni_util.hpp:185
Converts a string literal into a format string that will be parsed at compile time and converted into...
Definition printf.h:50
Definition StringMap.hpp:773
constexpr bool is_qualification_convertible_v
Definition jni_util.hpp:227
typename copy_cv< CvSrc, Dest >::type copy_cv_t
Definition jni_util.hpp:224
Java Native Interface (JNI) utility functions.
Definition jni_util.hpp:28
jbyteArray MakeJByteArray(JNIEnv *env, std::span< const uint8_t > str)
Convert a span into a jbyteArray.
Definition jni_util.hpp:533
jstring MakeJString(JNIEnv *env, std::string_view str)
Convert a UTF8 string into a jstring.
Definition jni_util.hpp:431
jintArray MakeJIntArray(JNIEnv *env, std::span< const T > arr)
Convert a span to a jintArray.
Definition jni_util.hpp:484
jbooleanArray MakeJBooleanArray(JNIEnv *env, std::span< const int > arr)
Convert an array of integers into a jbooleanArray.
Definition jni_util.hpp:549
jlongArray MakeJLongArray(JNIEnv *env, const T &arr)
Definition jni_util.hpp:614
std::string GetJavaStackTrace(JNIEnv *env, std::string *func=nullptr, std::string_view excludeFuncPrefix={})
Gets a Java stack trace.
Definition jni_util.hpp:860
jobjectArray MakeJStringArray(JNIEnv *env, std::span< const std::string > arr)
Convert an array of std::string into a jarray of jstring.
Definition jni_util.hpp:630
detail::JSpanBase< T, false, Extent > JSpan
Definition jni_util.hpp:416
detail::JSpanBase< T, true, Extent > CriticalJSpan
Definition jni_util.hpp:419
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.
bool Boolean
Definition ConvertUTF.hpp:130
constexpr WPI_String make_string(std::string_view view)
Converts a string_view to a WPI_String.
Definition string.hpp:24
void print(wpi::util::raw_ostream &os, const S &format_str, Args &&... args)
Prints formatted data to the stream os.
Definition raw_ostream.hpp:24
A const UTF8 string.
Definition string.h:12
Definition jni_util.hpp:79
JClass * cls
Definition jni_util.hpp:81
const char * name
Definition jni_util.hpp:80
Definition jni_util.hpp:974
JException * cls
Definition jni_util.hpp:976
const char * name
Definition jni_util.hpp:975
Definition jni_util.hpp:183
Definition jni_util.hpp:441
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition jni_util.hpp:442
Definition jni_util.hpp:213
U2 type
Definition jni_util.hpp:220