Files
allwpilib/wpiutil/src/main/native/include/wpi/util/jni_util.hpp

981 lines
27 KiB
C++
Raw Normal View History

// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
2018-05-13 17:09:56 -07:00
#ifndef WPIUTIL_WPI_JNI_UTIL_H_
#define WPIUTIL_WPI_JNI_UTIL_H_
2017-10-21 20:31:20 -07:00
#include <jni.h>
#include <concepts>
2017-10-21 20:31:20 -07:00
#include <queue>
#include <span>
#include <string>
#include <string_view>
2017-10-21 20:31:20 -07:00
#include <utility>
#include <vector>
2025-11-07 19:56:21 -05:00
#include "wpi/util/ConvertUTF.hpp"
#include "wpi/util/SafeThread.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/StringExtras.hpp"
#include "wpi/util/mutex.hpp"
#include "wpi/util/print.hpp"
#include "wpi/util/raw_ostream.hpp"
#include "wpi/util/string.h"
/** Java Native Interface (JNI) utility functions */
namespace wpi::java {
/**
* Gets a Java stack trace.
*
* Also provides the last function in the stack trace not starting with
* excludeFuncPrefix (useful for e.g. finding the first user call to a series of
* library functions).
*
* @param env JRE environment.
* @param func Storage for last function in the stack trace not starting with
* excludeFuncPrefix.
* @param excludeFuncPrefix Prefix for functions to ignore in stack trace.
*/
std::string GetJavaStackTrace(JNIEnv* env, std::string* func = nullptr,
std::string_view excludeFuncPrefix = {});
2016-12-14 23:32:03 -08:00
/**
* Finds a class and keeps it as a global reference.
*
* Use with caution, as the destructor does NOT call DeleteGlobalRef due to
* potential shutdown issues with doing so.
*/
class JClass {
public:
JClass() = default;
JClass(JNIEnv* env, const char* name) {
jclass local = env->FindClass(name);
if (!local) {
return;
}
m_cls = static_cast<jclass>(env->NewGlobalRef(local));
env->DeleteLocalRef(local);
}
2017-10-21 20:31:20 -07:00
void free(JNIEnv* env) {
if (m_cls) {
env->DeleteGlobalRef(m_cls);
}
m_cls = nullptr;
}
explicit operator bool() const { return m_cls; }
operator jclass() const { return m_cls; }
protected:
jclass m_cls = nullptr;
};
struct JClassInit {
const char* name;
JClass* cls;
};
2018-05-13 17:09:56 -07:00
template <typename T>
2018-04-29 13:29:07 -07:00
class JGlobal {
public:
JGlobal() = default;
JGlobal(JNIEnv* env, T obj) {
m_cls = static_cast<T>(env->NewGlobalRef(obj));
}
void free(JNIEnv* env) {
if (m_cls) {
env->DeleteGlobalRef(m_cls);
}
2018-04-29 13:29:07 -07:00
m_cls = nullptr;
}
explicit operator bool() const { return m_cls; }
operator T() const { return m_cls; } // NOLINT
2018-04-29 13:29:07 -07:00
protected:
T m_cls = nullptr;
};
/**
* Container class for cleaning up Java local references.
*
* The destructor calls DeleteLocalRef.
*/
template <typename T>
class JLocal {
public:
2017-10-21 20:31:20 -07:00
JLocal(JNIEnv* env, T obj) : m_env(env), m_obj(obj) {}
JLocal(const JLocal&) = delete;
JLocal(JLocal&& oth) : m_env(oth.m_env), m_obj(oth.m_obj) {
oth.m_obj = nullptr;
}
JLocal& operator=(const JLocal&) = delete;
JLocal& operator=(JLocal&& oth) {
m_env = oth.m_env;
m_obj = oth.m_obj;
oth.m_obj = nullptr;
return *this;
}
~JLocal() {
if (m_obj) {
m_env->DeleteLocalRef(m_obj);
}
}
operator T() { return m_obj; } // NOLINT
T obj() { return m_obj; }
private:
2017-10-21 20:31:20 -07:00
JNIEnv* m_env;
T m_obj;
};
//
// Conversions from Java objects to C++
//
/**
* Java string (jstring) reference.
*
* The string is provided as UTF8. This is not actually a reference, as it makes
* a copy of the string characters, but it's named this way for consistency.
*/
class JStringRef {
public:
2017-10-21 20:31:20 -07:00
JStringRef(JNIEnv* env, jstring str) {
if (str) {
jsize size = env->GetStringLength(str);
2017-10-21 20:31:20 -07:00
const jchar* chars = env->GetStringCritical(str, nullptr);
if (chars) {
convertUTF16ToUTF8String(std::span<const jchar>(chars, size), m_str);
env->ReleaseStringCritical(str, chars);
}
} else {
wpi::print(stderr, "JStringRef was passed a null pointer at\n",
GetJavaStackTrace(env));
}
// Ensure str is null-terminated.
m_str.push_back('\0');
m_str.pop_back();
}
operator std::string_view() const { return m_str.str(); } // NOLINT
std::string_view str() const { return m_str.str(); }
const char* c_str() const { return m_str.data(); }
size_t size() const { return m_str.size(); }
Change C APIs to a unified string implementation (#6299) Currently in the entire C API of WPILib we have ~8 different ways of handling strings. The C API actually isn't built for pure C callers (We don't actually have any of those). Instead, they're built for interop between languages like LabVIEW and C# which can talk to C API's directly. For output parameters, the choice was fairly obvious. An output struct containing a const string pointer and a length makes the most sense. Its easy to use these from most other languages, and doesn't require special null termination handling. Freeing these is also easy, as if you ever receive one of these string structures, theres just a single function call to free it. Input parameters are a bit more complex. To be used from pure C, and from LabVIEW, a null terminated string is the best in most cases. However, null terminated strings in general have a lot of downsides. Additionally, from LabVIEW there are other considerations around encoding that having a wrapper struct helps make a bit easier. From a language like C#, a wrapper struct is by far the easiest, as custom marshalling can make it trivial to marshal both UTF8 and UTF16 strings down. The final consideration is its nice to have an identical concept for both input and output. It makes the rules fairly easy to understand. WPILib will not have any APIs that manipulate a string allocated externally. This means WPI_String can be const, as across the boundary it is always const. If a WPILib API takes a const WPI_String*, WPILib will not manipulate or attempt to free that string, and that string is treated as an input. It is up to the caller to handle that memory, WPILib will never hold onto that memory longer than the call. If a WPILib API takes a WPI_String*, that string is an output. WPILib will allocate that API with WPI_AllocateString(), fill in the string, and return to the caller. When the caller is done with the string, they must free it with WPI_FreeString(). If an output struct contains a WPI_String member, that member is considered read only, and should not be explicitly freed. The caller should call the free function for that struct. If an array of WPI_Strings are returned, each individual string is considered read only, and should not be explicitly freed. The free function for that array should be called by the caller. If an input struct containing a WPI_String, or an input array of WPI_Strings is passed to WPILib, the individual strings will not be manipulated or freed by WPILib, and the caller owns and should free that memory. Callbacks also follow these rules. The most common is a callback either getting passed a const WPI_String* or a struct containing a WPI_String. In both of these cases, the callback target should consider these strings read only, and not attempt to free them or manipulate them.
2024-05-13 05:35:14 -07:00
WPI_String wpi_str() const { return wpi::make_string(str()); }
private:
SmallString<128> m_str;
};
namespace detail {
template <typename T>
struct ArrayHelper {};
#define WPI_JNI_ARRAYHELPER(T, F) \
template <> \
struct ArrayHelper<T> { \
using jarray_type = T##Array; \
static T* GetArrayElements(JNIEnv* env, jarray_type jarr) { \
return env->Get##F##ArrayElements(jarr, nullptr); \
} \
static void ReleaseArrayElements(JNIEnv* env, jarray_type jarr, T* elems, \
jint mode) { \
env->Release##F##ArrayElements(jarr, elems, mode); \
} \
};
WPI_JNI_ARRAYHELPER(jboolean, Boolean)
WPI_JNI_ARRAYHELPER(jbyte, Byte)
WPI_JNI_ARRAYHELPER(jshort, Short)
WPI_JNI_ARRAYHELPER(jint, Int)
WPI_JNI_ARRAYHELPER(jlong, Long)
WPI_JNI_ARRAYHELPER(jfloat, Float)
WPI_JNI_ARRAYHELPER(jdouble, Double)
#undef WPI_JNI_ARRAYHELPER
template <typename T>
concept JArrayType =
requires { typename ArrayHelper<std::remove_cv_t<T>>::jarray_type; };
template <typename CvSrc, typename Dest>
struct copy_cv {
private:
using U0 = std::remove_cv_t<Dest>;
using U1 = std::conditional_t<std::is_const_v<CvSrc>, const U0, U0>;
using U2 = std::conditional_t<std::is_volatile_v<CvSrc>, volatile U1, U1>;
public:
using type = U2;
};
template <typename CvSrc, typename Dest>
using copy_cv_t = typename copy_cv<CvSrc, Dest>::type;
template <typename From, typename To>
constexpr bool is_qualification_convertible_v =
!(std::is_const_v<From> && !std::is_const_v<To>) &&
!(std::is_volatile_v<From> && !std::is_volatile_v<To>);
/**
* Helper class for working with JNI arrays.
*
* This class exposes an is_valid() member and an explicit conversion to bool
* which indicate if the span is valid. Operations on invalid spans are
* undefined.
*
* Note that Set<PrimitiveType>ArrayRegion may be faster for pure writes since
* it avoids copying the elements from Java to C++.
*
* @tparam T The element type of the array (e.g., jdouble).
* @tparam IsCritical If true, Get/ReleasePrimitiveArrayCritical will be used
* instead of Get/Release\<PrimitiveType\>ArrayElements.
* @tparam Size The number of elements in the span.
*/
template <JArrayType T, bool IsCritical, size_t Size = std::dynamic_extent>
class JSpanBase {
using ArrHelper = ArrayHelper<std::remove_cv_t<T>>;
using jarray_type = typename ArrHelper::jarray_type;
public:
JSpanBase(const JSpanBase&) = delete;
JSpanBase& operator=(const JSpanBase&) = delete;
JSpanBase(JSpanBase&& other)
: m_valid{other.m_valid},
m_env{other.m_env},
m_jarr{other.m_jarr},
m_size{other.m_size},
m_elements{other.m_elements} {
other.m_jarr = nullptr;
other.m_elements = nullptr;
}
JSpanBase& operator=(JSpanBase&& other) {
m_valid = other.m_valid;
m_env = other.m_env;
m_jarr = other.m_jarr;
m_size = other.m_size;
m_elements = other.m_elements;
other.m_valid = false;
other.m_jarr = nullptr;
other.m_elements = nullptr;
return *this;
}
JSpanBase(JNIEnv* env, jobject bb, size_t size)
requires(!IsCritical)
: m_valid{Size == std::dynamic_extent || size == Size},
m_env{env},
m_jarr{nullptr},
m_size{size},
m_elements{static_cast<std::remove_cv_t<T>*>(
bb ? env->GetDirectBufferAddress(bb) : nullptr)} {
if (!bb) {
wpi::print(stderr, "JSpan was passed a null pointer at\n",
GetJavaStackTrace(env));
}
}
JSpanBase(JNIEnv* env, jarray_type jarr, size_t size)
: m_valid{Size == std::dynamic_extent || size == Size},
m_env{env},
m_jarr{jarr},
m_size{size},
m_elements{nullptr} {
if (jarr) {
if constexpr (IsCritical) {
m_elements = static_cast<std::remove_cv_t<T>*>(
env->GetPrimitiveArrayCritical(jarr, nullptr));
} else {
m_elements = ArrHelper::GetArrayElements(env, jarr);
}
} else {
wpi::print(stderr, "JSpan was passed a null pointer at\n",
GetJavaStackTrace(env));
}
}
JSpanBase(JNIEnv* env, jarray_type jarr)
: JSpanBase(env, jarr, jarr ? env->GetArrayLength(jarr) : 0) {}
~JSpanBase() {
if (m_jarr && m_elements) {
constexpr jint mode = std::is_const_v<T> ? JNI_ABORT : 0;
if constexpr (IsCritical) {
m_env->ReleasePrimitiveArrayCritical(m_jarr, m_elements, mode);
} else {
ArrHelper::ReleaseArrayElements(m_env, m_jarr, m_elements, mode);
}
}
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator std::span<T, Size>() const { return array(); }
std::span<T, Size> array() const {
// If Size is dynamic_extent, can return empty span
// Unfortunately, sized spans will return a span over nullptr if m_elements
// is nullptr
if constexpr (Size == std::dynamic_extent) {
if (!m_elements) {
return {};
}
}
return std::span<T, Size>{m_elements, m_size};
}
T* begin() const { return m_elements; }
T* end() const { return m_elements + m_size; }
bool is_valid() const { return m_valid && m_elements != nullptr; }
explicit operator bool() const { return is_valid(); }
T* data() const { return m_elements; }
size_t size() const { return m_size; }
const T& operator[](size_t i) const { return m_elements[i]; }
T& operator[](size_t i)
requires(!std::is_const_v<T>)
{
return m_elements[i];
}
// Provide std::string_view and span<const uint8_t> conversions for jbyte
operator std::string_view() const
requires std::is_same_v<std::remove_cv_t<T>, jbyte>
{
return str();
}
std::string_view str() const
requires std::is_same_v<std::remove_cv_t<T>, jbyte>
{
auto arr = array();
if (arr.empty()) {
return {};
}
return {reinterpret_cast<const char*>(arr.data()), arr.size()};
}
std::span<copy_cv_t<T, uint8_t>, Size> uarray() const
requires std::is_same_v<std::remove_cv_t<T>, jbyte>
{
auto arr = array();
if (arr.empty()) {
return {};
}
return {reinterpret_cast<const uint8_t*>(arr.data()), arr.size()};
}
// Support both "long long" and "long" on 64-bit systems
template <typename U>
requires(sizeof(U) == sizeof(jlong) && std::integral<U> &&
is_qualification_convertible_v<T, U>)
operator std::span<U, Size>() const
requires std::is_same_v<std::remove_cv_t<T>, jlong>
{
auto arr = array();
if (arr.empty()) {
return {};
}
return {reinterpret_cast<U*>(arr.data()), arr.size()};
}
// FIXME doxygen gives error parsing initializer list
//! @cond Doxygen_Suppress
private:
bool m_valid;
2017-10-21 20:31:20 -07:00
JNIEnv* m_env;
jarray_type m_jarr = nullptr;
size_t m_size;
std::remove_cv_t<T>* m_elements;
//! @endcond
};
} // namespace detail
template <typename T, size_t Extent = std::dynamic_extent>
using JSpan = detail::JSpanBase<T, false, Extent>;
template <typename T, size_t Extent = std::dynamic_extent>
using CriticalJSpan = detail::JSpanBase<T, true, Extent>;
//
// Conversions from C++ to Java objects
//
/**
* Convert a UTF8 string into a jstring.
*
* @param env JRE environment.
* @param str String to convert.
*/
inline jstring MakeJString(JNIEnv* env, std::string_view str) {
SmallVector<UTF16, 128> chars;
convertUTF8ToUTF16String(str, chars);
return env->NewString(chars.begin(), chars.size());
}
// details for MakeJIntArray
namespace detail {
template <typename T>
struct ConvertIntArray {
static jintArray ToJava(JNIEnv* env, std::span<const T> arr) {
if constexpr (sizeof(T) == sizeof(jint) && std::integral<T>) {
// Fast path (use SetIntArrayRegion).
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
}
env->SetIntArrayRegion(jarr, 0, arr.size(),
reinterpret_cast<const jint*>(arr.data()));
return jarr;
} else {
// Slow path (get primitive array and set individual elements).
//
// This is used if the input type is not an integer of the same size (note
// signed/unsigned is ignored).
jintArray jarr = env->NewIntArray(arr.size());
if (!jarr) {
return nullptr;
}
jint* elements =
static_cast<jint*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
elements[i] = static_cast<jint>(arr[i]);
}
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
}
};
} // namespace detail
/**
* Convert a span to a jintArray.
*
* @param env JRE environment.
* @param arr Span to convert.
*/
template <typename T>
inline jintArray MakeJIntArray(JNIEnv* env, std::span<const T> arr) {
return detail::ConvertIntArray<T>::ToJava(env, arr);
}
/**
* Convert a span to a jintArray.
*
* @param env JRE environment.
* @param arr Span to convert.
*/
template <typename T>
inline jintArray MakeJIntArray(JNIEnv* env, std::span<T> arr) {
return detail::ConvertIntArray<T>::ToJava(env, arr);
}
/**
* Convert a SmallVector to a jintArray.
*
* This is required in addition to ArrayRef because template resolution occurs
* prior to implicit conversions.
*
* @param env JRE environment.
* @param arr SmallVector to convert.
*/
template <typename T>
inline jintArray MakeJIntArray(JNIEnv* env, const SmallVectorImpl<T>& arr) {
return detail::ConvertIntArray<T>::ToJava(env, arr);
}
/**
* Convert a std::vector to a jintArray.
*
* This is required in addition to ArrayRef because template resolution occurs
* prior to implicit conversions.
*
* @param env JRE environment.
* @param arr SmallVector to convert.
*/
template <typename T>
2017-10-21 20:31:20 -07:00
inline jintArray MakeJIntArray(JNIEnv* env, const std::vector<T>& arr) {
return detail::ConvertIntArray<T>::ToJava(env, arr);
}
/**
* Convert a span into a jbyteArray.
*
* @param env JRE environment.
* @param str span to convert.
*/
inline jbyteArray MakeJByteArray(JNIEnv* env, std::span<const uint8_t> str) {
jbyteArray jarr = env->NewByteArray(str.size());
if (!jarr) {
return nullptr;
}
env->SetByteArrayRegion(jarr, 0, str.size(),
2017-10-21 20:31:20 -07:00
reinterpret_cast<const jbyte*>(str.data()));
return jarr;
}
/**
* Convert an array of integers into a jbooleanArray.
*
* @param env JRE environment.
* @param arr Array to convert.
*/
inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const int> arr) {
jbooleanArray jarr = env->NewBooleanArray(arr.size());
if (!jarr) {
return nullptr;
}
2017-10-21 20:31:20 -07:00
jboolean* elements =
static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
}
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
/**
* Convert an array of booleans into a jbooleanArray.
*
* @param env JRE environment.
* @param arr Array to convert.
*/
inline jbooleanArray MakeJBooleanArray(JNIEnv* env, std::span<const bool> arr) {
jbooleanArray jarr = env->NewBooleanArray(arr.size());
if (!jarr) {
return nullptr;
}
2017-10-21 20:31:20 -07:00
jboolean* elements =
static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
elements[i] = arr[i] ? JNI_TRUE : JNI_FALSE;
}
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
// Other MakeJ*Array conversions.
#define WPI_JNI_MAKEJARRAY(T, F) \
inline T##Array MakeJ##F##Array(JNIEnv* env, std::span<const T> arr) { \
T##Array jarr = env->New##F##Array(arr.size()); \
if (!jarr) { \
return nullptr; \
} \
env->Set##F##ArrayRegion(jarr, 0, arr.size(), arr.data()); \
return jarr; \
}
WPI_JNI_MAKEJARRAY(jboolean, Boolean)
WPI_JNI_MAKEJARRAY(jbyte, Byte)
WPI_JNI_MAKEJARRAY(jshort, Short)
WPI_JNI_MAKEJARRAY(jfloat, Float)
WPI_JNI_MAKEJARRAY(jdouble, Double)
#undef WPI_JNI_MAKEJARRAY
template <class T>
requires(sizeof(typename T::value_type) == sizeof(jlong) &&
std::integral<typename T::value_type>)
inline jlongArray MakeJLongArray(JNIEnv* env, const T& arr) {
jlongArray jarr = env->NewLongArray(arr.size());
if (!jarr) {
return nullptr;
}
env->SetLongArrayRegion(jarr, 0, arr.size(),
reinterpret_cast<const jlong*>(arr.data()));
return jarr;
}
/**
* Convert an array of std::string into a jarray of jstring.
*
* @param env JRE environment.
* @param arr Array to convert.
*/
inline jobjectArray MakeJStringArray(JNIEnv* env,
std::span<const std::string> arr) {
static JClass stringCls{env, "java/lang/String"};
if (!stringCls) {
return nullptr;
}
jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
if (!jarr) {
return nullptr;
}
2017-10-21 20:31:20 -07:00
for (size_t i = 0; i < arr.size(); ++i) {
JLocal<jstring> elem{env, MakeJString(env, arr[i])};
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
/**
* Convert an array of std::string into a jarray of jstring.
*
* @param env JRE environment.
* @param arr Array to convert.
*/
inline jobjectArray MakeJStringArray(JNIEnv* env,
std::span<std::string_view> arr) {
static JClass stringCls{env, "java/lang/String"};
if (!stringCls) {
return nullptr;
}
jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
if (!jarr) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
JLocal<jstring> elem{env, MakeJString(env, arr[i])};
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
/**
* Generic callback thread implementation.
*
* JNI's AttachCurrentThread() creates a Java Thread object on every
* invocation, which is both time inefficient and causes issues with Eclipse
* (which tries to keep a thread list up-to-date and thus gets swamped).
*
* Instead, this class attaches just once. When a hardware notification
* occurs, a condition variable wakes up this thread and this thread actually
* makes the call into Java.
*
* The template parameter T is the message being passed to the callback, but
* also needs to provide the following functions:
* static JavaVM* GetJVM();
* static const char* GetName();
* void CallJava(JNIEnv *env, jobject func, jmethodID mid);
*/
template <typename T>
class JCallbackThread : public SafeThread {
public:
void Main() override;
std::queue<T> m_queue;
jobject m_func = nullptr;
jmethodID m_mid;
};
template <typename T>
class JCallbackManager : public SafeThreadOwner<JCallbackThread<T>> {
public:
JCallbackManager() { this->SetJoinAtExit(false); }
void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
template <typename... Args>
void Send(Args&&... args);
};
template <typename T>
void JCallbackManager<T>::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
auto thr = this->GetThread();
if (!thr) {
return;
}
// free global reference
if (thr->m_func) {
env->DeleteGlobalRef(thr->m_func);
}
// create global reference
thr->m_func = env->NewGlobalRef(func);
thr->m_mid = mid;
}
template <typename T>
template <typename... Args>
void JCallbackManager<T>::Send(Args&&... args) {
auto thr = this->GetThread();
if (!thr) {
return;
}
thr->m_queue.emplace(std::forward<Args>(args)...);
thr->m_cond.notify_one();
}
template <typename T>
void JCallbackThread<T>::Main() {
2017-10-21 20:31:20 -07:00
JNIEnv* env;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_2;
args.name = const_cast<char*>(T::GetName());
args.group = nullptr;
2017-10-21 20:31:20 -07:00
jint rs = T::GetJVM()->AttachCurrentThreadAsDaemon(
reinterpret_cast<void**>(&env), &args);
if (rs != JNI_OK) {
return;
}
std::unique_lock lock(m_mutex);
while (m_active) {
m_cond.wait(lock, [&] { return !(m_active && m_queue.empty()); });
if (!m_active) {
break;
}
while (!m_queue.empty()) {
if (!m_active) {
break;
}
auto item = std::move(m_queue.front());
m_queue.pop();
auto func = m_func;
auto mid = m_mid;
lock.unlock(); // don't hold mutex during callback execution
item.CallJava(env, func, mid);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
lock.lock();
}
}
JavaVM* jvm = T::GetJVM();
if (jvm) {
jvm->DetachCurrentThread();
}
}
template <typename T>
class JSingletonCallbackManager : public JCallbackManager<T> {
public:
static JSingletonCallbackManager<T>& GetInstance() {
static JSingletonCallbackManager<T> instance;
return instance;
}
};
inline std::string GetJavaStackTrace(JNIEnv* env, std::string_view skipPrefix) {
// create a throwable
static JClass throwableCls(env, "java/lang/Throwable");
if (!throwableCls) {
return "";
}
static jmethodID constructorId = nullptr;
if (!constructorId) {
constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
}
JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
// retrieve information from the exception.
// get method id
// getStackTrace returns an array of StackTraceElement
static jmethodID getStackTraceId = nullptr;
if (!getStackTraceId) {
getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
"()[Ljava/lang/StackTraceElement;");
}
// call getStackTrace
JLocal<jobjectArray> stackTrace(
env, static_cast<jobjectArray>(
env->CallObjectMethod(throwable, getStackTraceId)));
if (!stackTrace) {
return "";
}
// get length of the array
jsize stackTraceLength = env->GetArrayLength(stackTrace);
// get toString methodId of StackTraceElement class
static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
if (!stackTraceElementCls) {
return "";
}
static jmethodID toStringId = nullptr;
if (!toStringId) {
toStringId = env->GetMethodID(stackTraceElementCls, "toString",
"()Ljava/lang/String;");
}
bool foundFirst = false;
std::string buf;
raw_string_ostream oss(buf);
for (jsize i = 0; i < stackTraceLength; i++) {
// add the result of toString method of each element in the result
JLocal<jobject> curStackTraceElement(
env, env->GetObjectArrayElement(stackTrace, i));
// call to string on the object
JLocal<jstring> stackElementString(
env, static_cast<jstring>(
env->CallObjectMethod(curStackTraceElement, toStringId)));
if (!stackElementString) {
return "";
}
// add a line to res
JStringRef elem(env, stackElementString);
if (!foundFirst) {
if (wpi::starts_with(elem, skipPrefix)) {
continue;
}
foundFirst = true;
}
oss << "\tat " << elem << '\n';
}
return oss.str();
}
inline std::string GetJavaStackTrace(JNIEnv* env, std::string* func,
std::string_view excludeFuncPrefix) {
2016-12-14 23:32:03 -08:00
// create a throwable
static JClass throwableCls(env, "java/lang/Throwable");
if (!throwableCls) {
return "";
}
2016-12-14 23:32:03 -08:00
static jmethodID constructorId = nullptr;
if (!constructorId) {
2016-12-14 23:32:03 -08:00
constructorId = env->GetMethodID(throwableCls, "<init>", "()V");
}
2016-12-14 23:32:03 -08:00
JLocal<jobject> throwable(env, env->NewObject(throwableCls, constructorId));
// retrieve information from the exception.
// get method id
// getStackTrace returns an array of StackTraceElement
static jmethodID getStackTraceId = nullptr;
if (!getStackTraceId) {
2016-12-14 23:32:03 -08:00
getStackTraceId = env->GetMethodID(throwableCls, "getStackTrace",
"()[Ljava/lang/StackTraceElement;");
}
2016-12-14 23:32:03 -08:00
// call getStackTrace
JLocal<jobjectArray> stackTrace(
env, static_cast<jobjectArray>(
env->CallObjectMethod(throwable, getStackTraceId)));
if (!stackTrace) {
return "";
}
2016-12-14 23:32:03 -08:00
// get length of the array
jsize stackTraceLength = env->GetArrayLength(stackTrace);
// get toString methodId of StackTraceElement class
static JClass stackTraceElementCls(env, "java/lang/StackTraceElement");
if (!stackTraceElementCls) {
return "";
}
2016-12-14 23:32:03 -08:00
static jmethodID toStringId = nullptr;
if (!toStringId) {
2016-12-14 23:32:03 -08:00
toStringId = env->GetMethodID(stackTraceElementCls, "toString",
"()Ljava/lang/String;");
}
2016-12-14 23:32:03 -08:00
bool haveLoc = false;
std::string buf;
raw_string_ostream oss(buf);
2016-12-14 23:32:03 -08:00
for (jsize i = 0; i < stackTraceLength; i++) {
// add the result of toString method of each element in the result
JLocal<jobject> curStackTraceElement(
env, env->GetObjectArrayElement(stackTrace, i));
// call to string on the object
JLocal<jstring> stackElementString(
env, static_cast<jstring>(
env->CallObjectMethod(curStackTraceElement, toStringId)));
if (!stackElementString) {
return "";
}
2016-12-14 23:32:03 -08:00
// add a line to res
JStringRef elem(env, stackElementString);
oss << elem << '\n';
if (func) {
// func is caller of immediate caller (if there was one)
// or, if we see it, the first user function
2017-10-21 20:31:20 -07:00
if (i == 1) {
2016-12-14 23:32:03 -08:00
*func = elem.str();
2017-10-21 20:31:20 -07:00
} else if (i > 1 && !haveLoc && !excludeFuncPrefix.empty() &&
!wpi::starts_with(elem, excludeFuncPrefix)) {
2016-12-14 23:32:03 -08:00
*func = elem.str();
haveLoc = true;
}
}
}
return oss.str();
}
/**
* Finds an exception class and keep it as a global reference.
*
* Similar to JClass, but provides Throw methods. Use with caution, as the
* destructor does NOT call DeleteGlobalRef due to potential shutdown issues
* with doing so.
*/
class JException : public JClass {
public:
JException() = default;
JException(JNIEnv* env, const char* name) : JClass(env, name) {
if (m_cls) {
m_constructor =
env->GetMethodID(m_cls, "<init>", "(Ljava/lang/String;)V");
}
}
void Throw(JNIEnv* env, jstring msg) {
jobject exception = env->NewObject(m_cls, m_constructor, msg);
env->Throw(static_cast<jthrowable>(exception));
}
void Throw(JNIEnv* env, std::string_view msg) {
Throw(env, MakeJString(env, msg));
}
explicit operator bool() const { return m_constructor; }
private:
jmethodID m_constructor = nullptr;
};
struct JExceptionInit {
const char* name;
JException* cls;
};
} // namespace wpi::java
2018-05-13 17:09:56 -07:00
#endif // WPIUTIL_WPI_JNI_UTIL_H_