WPILibC++ 2027.0.0-alpha-3
Loading...
Searching...
No Matches
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 // NOLINTNEXTLINE(google-explicit-constructor)
326 operator std::span<T, Size>() const { return array(); }
327
328 std::span<T, Size> array() const {
329 // If Size is dynamic_extent, can return empty span
330 // Unfortunately, sized spans will return a span over nullptr if m_elements
331 // is nullptr
332 if constexpr (Size == std::dynamic_extent) {
333 if (!m_elements) {
334 return {};
335 }
336 }
337 return std::span<T, Size>{m_elements, m_size};
338 }
339
340 T* begin() const { return m_elements; }
341
342 T* end() const { return m_elements + m_size; }
343
344 bool is_valid() const { return m_valid && m_elements != nullptr; }
345
346 explicit operator bool() const { return is_valid(); }
347
348 T* data() const { return m_elements; }
349
350 size_t size() const { return m_size; }
351
352 const T& operator[](size_t i) const { return m_elements[i]; }
353
354 T& operator[](size_t i)
355 requires(!std::is_const_v<T>)
356 {
357 return m_elements[i];
358 }
359
360 // Provide std::string_view and span<const uint8_t> conversions for jbyte
361
362 operator std::string_view() const
363 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
364 {
365 return str();
366 }
367
368 std::string_view str() const
369 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
370 {
371 auto arr = array();
372 if (arr.empty()) {
373 return {};
374 }
375 return {reinterpret_cast<const char*>(arr.data()), arr.size()};
376 }
377
378 std::span<copy_cv_t<T, uint8_t>, Size> uarray() const
379 requires std::is_same_v<std::remove_cv_t<T>, jbyte>
380 {
381 auto arr = array();
382 if (arr.empty()) {
383 return {};
384 }
385 return {reinterpret_cast<const uint8_t*>(arr.data()), arr.size()};
386 }
387
388 // Support both "long long" and "long" on 64-bit systems
389
390 template <typename U>
391 requires(sizeof(U) == sizeof(jlong) && std::integral<U> &&
393 operator std::span<U, Size>() const
394 requires std::is_same_v<std::remove_cv_t<T>, jlong>
395 {
396 auto arr = array();
397 if (arr.empty()) {
398 return {};
399 }
400 return {reinterpret_cast<U*>(arr.data()), arr.size()};
401 }
402
403 // FIXME doxygen gives error parsing initializer list
404 //! @cond Doxygen_Suppress
405 private:
406 bool m_valid;
407 JNIEnv* m_env;
408 jarray_type m_jarr = nullptr;
409 size_t m_size;
410 std::remove_cv_t<T>* m_elements;
411 //! @endcond
412};
413
414} // namespace detail
415
416template <typename T, size_t Extent = std::dynamic_extent>
418
419template <typename T, size_t Extent = std::dynamic_extent>
421
422//
423// Conversions from C++ to Java objects
424//
425
426/**
427 * Convert a UTF8 string into a jstring.
428 *
429 * @param env JRE environment.
430 * @param str String to convert.
431 */
432inline jstring MakeJString(JNIEnv* env, std::string_view str) {
433 SmallVector<UTF16, 128> chars;
434 convertUTF8ToUTF16String(str, chars);
435 return env->NewString(chars.begin(), chars.size());
436}
437
438// details for MakeJIntArray
439namespace detail {
440
441template <typename T>
443 static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
444 if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
445 // Fast path (use SetIntArrayRegion).
446 jintArray jarr = env->NewIntArray(arr.size());
447 if (!jarr) {
448 return nullptr;
449 }
450 env->SetIntArrayRegion(jarr, 0, arr.size(),
451 reinterpret_cast<const jint*>(arr.data()));
452 return jarr;
453 } else {
454 // Slow path (get primitive array and set individual elements).
455 //
456 // This is used if the input type is not an integer of the same size (note
457 // signed/unsigned is ignored).
458 jintArray jarr = env->NewIntArray(arr.size());
459 if (!jarr) {
460 return nullptr;
461 }
462 jint* elements =
463 static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
464 if (!elements) {
465 return nullptr;
466 }
467 for (size_t i = 0; i < arr.size(); ++i) {
468 elements[i] = static_cast<jint>(arr[i]);
469 }
470 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
471 return jarr;
472 }
473 }
474};
475
476} // namespace detail
477
478/**
479 * Convert a span to a jintArray.
480 *
481 * @param env JRE environment.
482 * @param arr Span to convert.
483 */
484template <typename T>
485inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
486 return detail::ConvertIntArray<T>::ToJava(env, arr);
487}
488
489/**
490 * Convert a span to a jintArray.
491 *
492 * @param env JRE environment.
493 * @param arr Span to convert.
494 */
495template <typename T>
496inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
497 return detail::ConvertIntArray<T>::ToJava(env, arr);
498}
499
500/**
501 * Convert a SmallVector to a jintArray.
502 *
503 * This is required in addition to ArrayRef because template resolution occurs
504 * prior to implicit conversions.
505 *
506 * @param env JRE environment.
507 * @param arr SmallVector to convert.
508 */
509template <typename T>
510inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
511 return detail::ConvertIntArray<T>::ToJava(env, arr);
512}
513
514/**
515 * Convert a std::vector to a jintArray.
516 *
517 * This is required in addition to ArrayRef because template resolution occurs
518 * prior to implicit conversions.
519 *
520 * @param env JRE environment.
521 * @param arr SmallVector to convert.
522 */
523template <typename T>
524inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
525 return detail::ConvertIntArray<T>::ToJava(env, arr);
526}
527
528/**
529 * Convert a span into a jbyteArray.
530 *
531 * @param env JRE environment.
532 * @param str span to convert.
533 */
534inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
535 jbyteArray jarr = env->NewByteArray(str.size());
536 if (!jarr) {
537 return nullptr;
538 }
539 env->SetByteArrayRegion(jarr, 0, str.size(),
540 reinterpret_cast<const jbyte*>(str.data()));
541 return jarr;
542}
543
544/**
545 * Convert an array of integers into a jbooleanArray.
546 *
547 * @param env JRE environment.
548 * @param arr Array to convert.
549 */
550inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
551 jbooleanArray jarr = env->NewBooleanArray(arr.size());
552 if (!jarr) {
553 return nullptr;
554 }
555 jboolean* elements =
556 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
557 if (!elements) {
558 return nullptr;
559 }
560 for (size_t i = 0; i < arr.size(); ++i) {
561 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
562 }
563 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
564 return jarr;
565}
566
567/**
568 * Convert an array of booleans into a jbooleanArray.
569 *
570 * @param env JRE environment.
571 * @param arr Array to convert.
572 */
573inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
574 jbooleanArray jarr = env->NewBooleanArray(arr.size());
575 if (!jarr) {
576 return nullptr;
577 }
578 jboolean* elements =
579 static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
580 if (!elements) {
581 return nullptr;
582 }
583 for (size_t i = 0; i < arr.size(); ++i) {
584 elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
585 }
586 env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
587 return jarr;
588}
589
590// Other MakeJ*Array conversions.
591
592#define WPI_JNI_MAKEJARRAY(T, F) \
593 inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
594 T##Array jarr = env->New##F##Array(arr.size()); \
595 if (!jarr) { \
596 return nullptr; \
597 } \
598 env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
599 return jarr; \
600 }
601
603WPI_JNI_MAKEJARRAY(jbyte, Byte)
604WPI_JNI_MAKEJARRAY(jshort, Short)
605WPI_JNI_MAKEJARRAY(jfloat, Float)
606WPI_JNI_MAKEJARRAY(jdouble, Double)
607
608#undef WPI_JNI_MAKEJARRAY
609
610template <class T>
611 requires(sizeof(typename T::value_type) == sizeof(jlong) &&
612 std::integral<typename T::value_type>)
613inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
614 jlongArray jarr = env->NewLongArray(arr.size());
615 if (!jarr) {
616 return nullptr;
617 }
618 env->SetLongArrayRegion(jarr, 0, arr.size(),
619 reinterpret_cast<const jlong*>(arr.data()));
620 return jarr;
621}
622
623/**
624 * Convert an array of std::string into a jarray of jstring.
625 *
626 * @param env JRE environment.
627 * @param arr Array to convert.
628 */
629inline jobjectArray MakeJStringArray(JNIEnv* env,
630 std::span<const std::string> arr) {
631 static JClass stringCls{env, "java/lang/String"};
632 if (!stringCls) {
633 return nullptr;
634 }
635 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
636 if (!jarr) {
637 return nullptr;
638 }
639 for (size_t i = 0; i < arr.size(); ++i) {
640 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
641 env->SetObjectArrayElement(jarr, i, elem.obj());
642 }
643 return jarr;
644}
645
646/**
647 * Convert an array of std::string into a jarray of jstring.
648 *
649 * @param env JRE environment.
650 * @param arr Array to convert.
651 */
652inline jobjectArray MakeJStringArray(JNIEnv* env,
653 std::span<std::string_view> arr) {
654 static JClass stringCls{env, "java/lang/String"};
655 if (!stringCls) {
656 return nullptr;
657 }
658 jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
659 if (!jarr) {
660 return nullptr;
661 }
662 for (size_t i = 0; i < arr.size(); ++i) {
663 JLocal<jstring> elem{env, MakeJString(env, arr[i])};
664 env->SetObjectArrayElement(jarr, i, elem.obj());
665 }
666 return jarr;
667}
668
669/**
670 * Generic callback thread implementation.
671 *
672 * JNI's AttachCurrentThread() creates a Java Thread object on every
673 * invocation, which is both time inefficient and causes issues with Eclipse
674 * (which tries to keep a thread list up-to-date and thus gets swamped).
675 *
676 * Instead, this class attaches just once. When a hardware notification
677 * occurs, a condition variable wakes up this thread and this thread actually
678 * makes the call into Java.
679 *
680 * The template parameter T is the message being passed to the callback, but
681 * also needs to provide the following functions:
682 * static JavaVM* GetJVM();
683 * static const char* GetName();
684 * void CallJava(JNIEnv *env, jobject func, jmethodID mid);
685 */
686template <typename T>
688 public:
689 void Main() override;
690
691 std::queue<T> m_queue;
692 jobject m_func = nullptr;
693 jmethodID m_mid;
694};
695
696template <typename T>
697class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
698 public:
699 JCallbackManager() { this->SetJoinAtExit(false); }
700 void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
701
702 template <typename... Args>
703 void Send(Args&&... args);
704};
705
706template <typename T>
707void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
708 auto thr = this->GetThread();
709 if (!thr) {
710 return;
711 }
712 // free global reference
713 if (thr->m_func) {
714 env->DeleteGlobalRef(thr->m_func);
715 }
716 // create global reference
717 thr->m_func = env->NewGlobalRef(func);
718 thr->m_mid = mid;
719}
720
721template <typename T>
722template <typename... Args>
723void JCallbackManager<T>::Send(Args&&... args) {
724 auto thr = this->GetThread();
725 if (!thr) {
726 return;
727 }
728 thr->m_queue.emplace(std::forward<Args>(args)...);
729 thr->m_cond.notify_one();
730}
731
732template <typename T>
734 JNIEnv* env;
735 JavaVMAttachArgs args;
736 args.version = JNI_VERSION_1_2;
737 args.name = const_cast<char*>(T::GetName());
738 args.group = nullptr;
739 jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
740 reinterpret_cast<void**>(&env), &args);
741 if (rs != JNI_OK) {
742 return;
743 }
744
745 std::unique_lock lock(m_mutex);
746 while (m_active) {
747 m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
748 if (!m_active) {
749 break;
750 }
751 while (!m_queue.empty()) {
752 if (!m_active) {
753 break;
754 }
755 auto item = std::move(m_queue.front());
756 m_queue.pop();
757 auto func = m_func;
758 auto mid = m_mid;
759 lock.unlock(); // don't hold mutex during callback execution
760 item.CallJava(env, func, mid);
761 if (env->ExceptionCheck()) {
762 env->ExceptionDescribe();
763 env->ExceptionClear();
764 }
765 lock.lock();
766 }
767 }
768
769 JavaVM* jvm = T::GetJVM();
770 if (jvm) {
771 jvm->DetachCurrentThread();
772 }
773}
774
775template <typename T>
777 public:
779 static JSingletonCallbackManager<T> instance;
780 return instance;
781 }
782};
783
784inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
785 // create a throwable
786 static JClass throwableCls(env, "java/lang/Throwable");
787 if (!throwableCls) {
788 return "";
789 }
790 static jmethodID constructorId = nullptr;
791 if (!constructorId) {
792 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
793 }
794 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
795
796 // retrieve information from the exception.
797 // get method id
798 // getStackTrace returns an array of StackTraceElement
799 static jmethodID getStackTraceId = nullptr;
800 if (!getStackTraceId) {
801 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
802 "()[Ljava/lang/StackTraceElement;");
803 }
804
805 // call getStackTrace
806 JLocal<jobjectArray> stackTrace(
807 env, static_cast<jobjectArray>(
808 env->CallObjectMethod(throwable, getStackTraceId)));
809
810 if (!stackTrace) {
811 return "";
812 }
813
814 // get length of the array
815 jsize stackTraceLength = env->GetArrayLength(stackTrace);
816
817 // get toString methodId of StackTraceElement class
818 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
819 if (!stackTraceElementCls) {
820 return "";
821 }
822 static jmethodID toStringId = nullptr;
823 if (!toStringId) {
824 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
825 "()Ljava/lang/String;");
826 }
827
828 bool foundFirst = false;
829 std::string buf;
830 raw_string_ostream oss(buf);
831 for (jsize i = 0; i < stackTraceLength; i++) {
832 // add the result of toString method of each element in the result
833 JLocal<jobject> curStackTraceElement(
834 env, env->GetObjectArrayElement(stackTrace, i));
835
836 // call to string on the object
837 JLocal<jstring> stackElementString(
838 env, static_cast<jstring>(
839 env->CallObjectMethod(curStackTraceElement, toStringId)));
840
841 if (!stackElementString) {
842 return "";
843 }
844
845 // add a line to res
846 JStringRef elem(env, stackElementString);
847 if (!foundFirst) {
848 if (wpi::starts_with(elem, skipPrefix)) {
849 continue;
850 }
851 foundFirst = true;
852 }
853 oss << "\tat " << elem << '\n';
854 }
855
856 return oss.str();
857}
858
859inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
860 std::string_view excludeFuncPrefix) {
861 // create a throwable
862 static JClass throwableCls(env, "java/lang/Throwable");
863 if (!throwableCls) {
864 return "";
865 }
866 static jmethodID constructorId = nullptr;
867 if (!constructorId) {
868 constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
869 }
870 JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
871
872 // retrieve information from the exception.
873 // get method id
874 // getStackTrace returns an array of StackTraceElement
875 static jmethodID getStackTraceId = nullptr;
876 if (!getStackTraceId) {
877 getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
878 "()[Ljava/lang/StackTraceElement;");
879 }
880
881 // call getStackTrace
882 JLocal<jobjectArray> stackTrace(
883 env, static_cast<jobjectArray>(
884 env->CallObjectMethod(throwable, getStackTraceId)));
885
886 if (!stackTrace) {
887 return "";
888 }
889
890 // get length of the array
891 jsize stackTraceLength = env->GetArrayLength(stackTrace);
892
893 // get toString methodId of StackTraceElement class
894 static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
895 if (!stackTraceElementCls) {
896 return "";
897 }
898 static jmethodID toStringId = nullptr;
899 if (!toStringId) {
900 toStringId = env->GetMethodID(stackTraceElementCls, "toString",
901 "()Ljava/lang/String;");
902 }
903
904 bool haveLoc = false;
905 std::string buf;
906 raw_string_ostream oss(buf);
907 for (jsize i = 0; i < stackTraceLength; i++) {
908 // add the result of toString method of each element in the result
909 JLocal<jobject> curStackTraceElement(
910 env, env->GetObjectArrayElement(stackTrace, i));
911
912 // call to string on the object
913 JLocal<jstring> stackElementString(
914 env, static_cast<jstring>(
915 env->CallObjectMethod(curStackTraceElement, toStringId)));
916
917 if (!stackElementString) {
918 return "";
919 }
920
921 // add a line to res
922 JStringRef elem(env, stackElementString);
923 oss << elem << '\n';
924
925 if (func) {
926 // func is caller of immediate caller (if there was one)
927 // or, if we see it, the first user function
928 if (i == 1) {
929 *func = elem.str();
930 } else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
931 !wpi::starts_with(elem, excludeFuncPrefix)) {
932 *func = elem.str();
933 haveLoc = true;
934 }
935 }
936 }
937
938 return oss.str();
939}
940
941/**
942 * Finds an exception class and keep it as a global reference.
943 *
944 * Similar to JClass, but provides Throw methods. Use with caution, as the
945 * destructor does NOT call DeleteGlobalRef due to potential shutdown issues
946 * with doing so.
947 */
948class JException : public JClass {
949 public:
950 JException() = default;
951 JException(JNIEnv* env, const char* name) : JClass(env, name) {
952 if (m_cls) {
953 m_constructor =
954 env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
955 }
956 }
957
958 void Throw(JNIEnv* env, jstring msg) {
959 jobject exception = env->NewObject(m_cls, m_constructor, msg);
960 env->Throw(static_cast<jthrowable>(exception));
961 }
962
963 void Throw(JNIEnv* env, std::string_view msg) {
964 Throw(env, MakeJString(env, msg));
965 }
966
967 explicit operator bool() const { return m_constructor; }
968
969 private:
970 jmethodID m_constructor = nullptr;
971};
972
974 const char* name;
976};
977
978} // namespace wpi::java
979
980#endif // WPIUTIL_WPI_JNI_UTIL_H_
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
Definition SafeThread.h:33
Definition SafeThread.h:124
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.h:27
std::string_view str() const
Explicit conversion to std::string_view.
Definition SmallString.h:186
Definition sha1.h:30
void SetJoinAtExit(bool joinAtExit)
Definition SafeThread.h:106
Definition jni_util.h:697
JCallbackManager()
Definition jni_util.h:699
void SetFunc(JNIEnv *env, jobject func, jmethodID mid)
Definition jni_util.h:707
void Send(Args &&... args)
Definition jni_util.h:723
Generic callback thread implementation.
Definition jni_util.h:687
jmethodID m_mid
Definition jni_util.h:693
jobject m_func
Definition jni_util.h:692
std::queue< T > m_queue
Definition jni_util.h:691
void Main() override
Definition jni_util.h:733
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:948
void Throw(JNIEnv *env, std::string_view msg)
Definition jni_util.h:963
JException(JNIEnv *env, const char *name)
Definition jni_util.h:951
void Throw(JNIEnv *env, jstring msg)
Definition jni_util.h:958
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:776
static JSingletonCallbackManager< T > & GetInstance()
Definition jni_util.h:778
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
JSpanBase(const JSpanBase &)=delete
T & operator[](size_t i)
Definition jni_util.h:354
JSpanBase(JNIEnv *env, jarray_type jarr)
Definition jni_util.h:311
bool is_valid() const
Definition jni_util.h:344
T * end() const
Definition jni_util.h:342
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:378
T * begin() const
Definition jni_util.h:340
const T & operator[](size_t i) const
Definition jni_util.h:352
size_t size() const
Definition jni_util.h:350
T * data() const
Definition jni_util.h:348
JSpanBase(JNIEnv *env, jobject bb, size_t size)
Definition jni_util.h:278
std::string_view str() const
Definition jni_util.h:368
JSpanBase(JSpanBase &&other)
Definition jni_util.h:256
std::span< T, Size > array() const
Definition jni_util.h:328
JSpanBase & operator=(const JSpanBase &)=delete
~JSpanBase()
Definition jni_util.h:314
Definition jni_util.h:210
#define WPI_JNI_MAKEJARRAY(T, F)
Definition jni_util.h:592
#define WPI_JNI_ARRAYHELPER(T, F)
Definition jni_util.h:186
Converts a string literal into a format string that will be parsed at compile time and converted into...
Definition printf.h:50
Definition PointerIntPair.h:280
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:613
jintArray MakeJIntArray(JNIEnv *env, std::span< const T > arr)
Convert a span to a jintArray.
Definition jni_util.h:485
jstring MakeJString(JNIEnv *env, std::string_view str)
Convert a UTF8 string into a jstring.
Definition jni_util.h:432
std::string GetJavaStackTrace(JNIEnv *env, std::string *func=nullptr, std::string_view excludeFuncPrefix={})
Gets a Java stack trace.
Definition jni_util.h:859
jbooleanArray MakeJBooleanArray(JNIEnv *env, std::span< const int > arr)
Convert an array of integers into a jbooleanArray.
Definition jni_util.h:550
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:629
jbyteArray MakeJByteArray(JNIEnv *env, std::span< const uint8_t > str)
Convert a span into a jbyteArray.
Definition jni_util.h:534
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:34
bool Boolean
Definition ConvertUTF.h:130
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.
A const UTF8 string.
Definition string.h:15
Definition jni_util.h:80
const char * name
Definition jni_util.h:81
JClass * cls
Definition jni_util.h:82
Definition jni_util.h:973
JException * cls
Definition jni_util.h:975
const char * name
Definition jni_util.h:974
Definition jni_util.h:184
Definition jni_util.h:442
static jintArray ToJava(JNIEnv *env, std::span< const T > arr)
Definition jni_util.h:443
Definition jni_util.h:214
U2 type
Definition jni_util.h:221