blob: d235fd34ffc1e4a1852d7d50be43d4e405e2d395 [file] [log] [blame]
#include <jni.h>
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <mutex>
#include <sstream>
#include <queue>
#include <thread>
#include "edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h"
#include "ntcore.h"
#include "atomic_static.h"
//
// Globals and load/unload
//
// Used for callback.
static JavaVM *jvm = nullptr;
static jclass booleanCls = nullptr;
static jclass doubleCls = nullptr;
static jclass stringCls = nullptr;
static jclass connectionInfoCls = nullptr;
static jclass entryInfoCls = nullptr;
static jclass keyNotDefinedEx = nullptr;
static jclass persistentEx = nullptr;
// Thread-attached environment for listener callbacks.
static JNIEnv *listenerEnv = nullptr;
static void ListenerOnStart() {
if (!jvm) return;
JNIEnv *env;
if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env),
nullptr) != JNI_OK)
return;
if (!env || !env->functions) return;
listenerEnv = env;
}
static void ListenerOnExit() {
listenerEnv = nullptr;
if (!jvm) return;
jvm->DetachCurrentThread();
}
extern "C" {
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
// Cache references to classes
jclass local;
local = env->FindClass("java/lang/Boolean");
if (!local) return JNI_ERR;
booleanCls = static_cast<jclass>(env->NewGlobalRef(local));
if (!booleanCls) return JNI_ERR;
env->DeleteLocalRef(local);
local = env->FindClass("java/lang/Double");
if (!local) return JNI_ERR;
doubleCls = static_cast<jclass>(env->NewGlobalRef(local));
if (!doubleCls) return JNI_ERR;
env->DeleteLocalRef(local);
local = env->FindClass("java/lang/String");
if (!local) return JNI_ERR;
stringCls = static_cast<jclass>(env->NewGlobalRef(local));
if (!stringCls) return JNI_ERR;
env->DeleteLocalRef(local);
local = env->FindClass("edu/wpi/first/wpilibj/networktables/ConnectionInfo");
if (!local) return JNI_ERR;
connectionInfoCls = static_cast<jclass>(env->NewGlobalRef(local));
if (!connectionInfoCls) return JNI_ERR;
env->DeleteLocalRef(local);
local = env->FindClass("edu/wpi/first/wpilibj/networktables/EntryInfo");
if (!local) return JNI_ERR;
entryInfoCls = static_cast<jclass>(env->NewGlobalRef(local));
if (!entryInfoCls) return JNI_ERR;
env->DeleteLocalRef(local);
local =
env->FindClass("edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined");
keyNotDefinedEx = static_cast<jclass>(env->NewGlobalRef(local));
if (!keyNotDefinedEx) return JNI_ERR;
env->DeleteLocalRef(local);
local =
env->FindClass("edu/wpi/first/wpilibj/networktables/PersistentException");
persistentEx = static_cast<jclass>(env->NewGlobalRef(local));
if (!persistentEx) return JNI_ERR;
env->DeleteLocalRef(local);
// Initial configuration of listener start/exit
nt::SetListenerOnStart(ListenerOnStart);
nt::SetListenerOnExit(ListenerOnExit);
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
return;
// Delete global references
if (booleanCls) env->DeleteGlobalRef(booleanCls);
if (doubleCls) env->DeleteGlobalRef(doubleCls);
if (stringCls) env->DeleteGlobalRef(stringCls);
if (connectionInfoCls) env->DeleteGlobalRef(connectionInfoCls);
if (entryInfoCls) env->DeleteGlobalRef(entryInfoCls);
if (keyNotDefinedEx) env->DeleteGlobalRef(keyNotDefinedEx);
if (persistentEx) env->DeleteGlobalRef(persistentEx);
jvm = nullptr;
}
} // extern "C"
//
// Helper class to automatically clean up a local reference
//
template <typename T>
class JavaLocal {
public:
JavaLocal(JNIEnv *env, T obj) : m_env(env), m_obj(obj) {}
~JavaLocal() {
if (m_obj) m_env->DeleteLocalRef(m_obj);
}
operator T() { return m_obj; }
T obj() { return m_obj; }
private:
JNIEnv *m_env;
T m_obj;
};
//
// Helper class to create and clean up a global reference
//
template <typename T>
class JavaGlobal {
public:
JavaGlobal(JNIEnv *env, T obj)
: m_obj(static_cast<T>(env->NewGlobalRef(obj))) {}
~JavaGlobal() {
if (!jvm || nt::NotifierDestroyed()) return;
JNIEnv *env;
if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
JNI_OK)
return;
if (!env || !env->functions) return;
env->DeleteGlobalRef(m_obj);
jvm->DetachCurrentThread();
}
operator T() { return m_obj; }
T obj() { return m_obj; }
private:
T m_obj;
};
//
// Helper class to create and clean up a weak global reference
//
template <typename T>
class JavaWeakGlobal {
public:
JavaWeakGlobal(JNIEnv *env, T obj)
: m_obj(static_cast<T>(env->NewWeakGlobalRef(obj))) {}
~JavaWeakGlobal() {
if (!jvm || nt::NotifierDestroyed()) return;
JNIEnv *env;
if (jvm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
JNI_OK)
return;
if (!env || !env->functions) return;
env->DeleteWeakGlobalRef(m_obj);
jvm->DetachCurrentThread();
}
JavaLocal<T> obj(JNIEnv *env) {
return JavaLocal<T>(env, env->NewLocalRef(m_obj));
}
private:
T m_obj;
};
//
// Conversions from Java objects to C++
//
class JavaStringRef {
public:
JavaStringRef(JNIEnv *env, jstring str)
: m_env(env),
m_jstr(str),
m_str(env->GetStringUTFChars(str, nullptr)) {}
~JavaStringRef() { m_env->ReleaseStringUTFChars(m_jstr, m_str); }
operator nt::StringRef() const { return nt::StringRef(m_str); }
nt::StringRef str() const { return nt::StringRef(m_str); }
const char *c_str() const { return m_str; }
private:
JNIEnv *m_env;
jstring m_jstr;
const char *m_str;
};
class JavaByteRef {
public:
JavaByteRef(JNIEnv *env, jbyteArray jarr)
: m_env(env),
m_jarr(jarr),
m_elements(env->GetByteArrayElements(jarr, nullptr)),
m_size(env->GetArrayLength(jarr)) {}
~JavaByteRef() {
m_env->ReleaseByteArrayElements(m_jarr, m_elements, JNI_ABORT);
}
operator nt::StringRef() const {
return nt::StringRef(reinterpret_cast<char *>(m_elements), m_size);
}
private:
JNIEnv *m_env;
jbyteArray m_jarr;
jbyte *m_elements;
size_t m_size;
};
class JavaByteRefBB {
public:
JavaByteRefBB(JNIEnv *env, jobject bb, int len)
: m_elements(env->GetDirectBufferAddress(bb)), m_size(len) {}
operator nt::StringRef() const {
return nt::StringRef(reinterpret_cast<char *>(m_elements), m_size);
}
private:
void *m_elements;
size_t m_size;
};
std::shared_ptr<nt::Value> FromJavaRaw(JNIEnv *env, jbyteArray jarr) {
size_t len = env->GetArrayLength(jarr);
jbyte *elements =
static_cast<jbyte *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) return nullptr;
auto rv = nt::Value::MakeRaw(
nt::StringRef(reinterpret_cast<char *>(elements), len));
env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
return rv;
}
std::shared_ptr<nt::Value> FromJavaRawBB(JNIEnv *env, jobject jbb, int len) {
void* elements = env->GetDirectBufferAddress(jbb);
if (!elements) return nullptr;
auto rv = nt::Value::MakeRaw(
nt::StringRef(reinterpret_cast<char *>(elements), len));
return rv;
}
std::shared_ptr<nt::Value> FromJavaRpc(JNIEnv *env, jbyteArray jarr) {
size_t len = env->GetArrayLength(jarr);
jbyte *elements =
static_cast<jbyte *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) return nullptr;
auto rv = nt::Value::MakeRpc(
nt::StringRef(reinterpret_cast<char *>(elements), len));
env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
return rv;
}
std::shared_ptr<nt::Value> FromJavaBooleanArray(JNIEnv *env,
jbooleanArray jarr) {
size_t len = env->GetArrayLength(jarr);
std::vector<int> arr;
arr.reserve(len);
jboolean *elements =
static_cast<jboolean*>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) return nullptr;
for (size_t i = 0; i < len; ++i) arr.push_back(elements[i]);
env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
return nt::Value::MakeBooleanArray(arr);
}
std::shared_ptr<nt::Value> FromJavaDoubleArray(JNIEnv *env, jdoubleArray jarr) {
size_t len = env->GetArrayLength(jarr);
jdouble *elements =
static_cast<jdouble *>(env->GetPrimitiveArrayCritical(jarr, nullptr));
if (!elements) return nullptr;
auto rv = nt::Value::MakeDoubleArray(nt::ArrayRef<double>(elements, len));
env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT);
return rv;
}
std::shared_ptr<nt::Value> FromJavaStringArray(JNIEnv *env, jobjectArray jarr) {
size_t len = env->GetArrayLength(jarr);
std::vector<std::string> arr;
arr.reserve(len);
for (size_t i = 0; i < len; ++i) {
JavaLocal<jstring> elem(
env, static_cast<jstring>(env->GetObjectArrayElement(jarr, i)));
if (!elem) return nullptr;
arr.push_back(JavaStringRef(env, elem).str());
}
return nt::Value::MakeStringArray(std::move(arr));
}
//
// Conversions from C++ to Java objects
//
static inline jstring ToJavaString(JNIEnv *env, nt::StringRef str) {
// fastpath if string already null terminated; if not, need to make a copy
if (str.data()[str.size()] == '\0')
return env->NewStringUTF(str.data());
else
return env->NewStringUTF(str.str().c_str());
}
static jbyteArray ToJavaByteArray(JNIEnv *env, nt::StringRef str) {
jbyteArray jarr = env->NewByteArray(str.size());
if (!jarr) return nullptr;
env->SetByteArrayRegion(jarr, 0, str.size(),
reinterpret_cast<const jbyte *>(str.data()));
return jarr;
}
static jbooleanArray ToJavaBooleanArray(JNIEnv *env, nt::ArrayRef<int> arr)
{
jbooleanArray jarr = env->NewBooleanArray(arr.size());
if (!jarr) return nullptr;
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];
env->ReleasePrimitiveArrayCritical(jarr, elements, 0);
return jarr;
}
static jdoubleArray ToJavaDoubleArray(JNIEnv *env, nt::ArrayRef<double> arr)
{
jdoubleArray jarr = env->NewDoubleArray(arr.size());
if (!jarr) return nullptr;
env->SetDoubleArrayRegion(jarr, 0, arr.size(), arr.data());
return jarr;
}
static jobjectArray ToJavaStringArray(JNIEnv *env,
nt::ArrayRef<std::string> arr) {
jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
if (!jarr) return nullptr;
for (size_t i = 0; i < arr.size(); ++i) {
JavaLocal<jstring> elem(env, env->NewStringUTF(arr[i].c_str()));
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
static jobject ToJavaObject(JNIEnv *env, const nt::Value& value) {
static jmethodID booleanConstructor = nullptr;
static jmethodID doubleConstructor = nullptr;
if (!booleanConstructor)
booleanConstructor = env->GetMethodID(booleanCls, "<init>", "(Z)V");
if (!doubleConstructor)
doubleConstructor = env->GetMethodID(doubleCls, "<init>", "(D)V");
switch (value.type()) {
case NT_BOOLEAN:
return env->NewObject(booleanCls, booleanConstructor,
(jboolean)(value.GetBoolean() ? 1 : 0));
case NT_DOUBLE:
return env->NewObject(doubleCls, doubleConstructor,
(jdouble)value.GetDouble());
case NT_STRING:
return ToJavaString(env, value.GetString());
case NT_RAW:
return ToJavaByteArray(env, value.GetRaw());
case NT_BOOLEAN_ARRAY:
return ToJavaBooleanArray(env, value.GetBooleanArray());
case NT_DOUBLE_ARRAY:
return ToJavaDoubleArray(env, value.GetDoubleArray());
case NT_STRING_ARRAY:
return ToJavaStringArray(env, value.GetStringArray());
case NT_RPC:
return ToJavaByteArray(env, value.GetRpc());
default:
return nullptr;
}
}
static jobject ToJavaObject(JNIEnv *env, const nt::ConnectionInfo &info) {
static jmethodID constructor =
env->GetMethodID(connectionInfoCls, "<init>",
"(Ljava/lang/String;Ljava/lang/String;IJI)V");
JavaLocal<jstring> remote_id(env, ToJavaString(env, info.remote_id));
JavaLocal<jstring> remote_name(env, ToJavaString(env, info.remote_name));
return env->NewObject(connectionInfoCls, constructor, remote_id.obj(),
remote_name.obj(), (jint)info.remote_port,
(jlong)info.last_update, (jint)info.protocol_version);
}
static jobject ToJavaObject(JNIEnv *env, const nt::EntryInfo &info) {
static jmethodID constructor =
env->GetMethodID(entryInfoCls, "<init>", "(Ljava/lang/String;IIJ)V");
JavaLocal<jstring> name(env, ToJavaString(env, info.name));
return env->NewObject(entryInfoCls, constructor, name.obj(), (jint)info.type,
(jint)info.flags, (jlong)info.last_change);
}
//
// Exception throwers
//
static void ThrowTableKeyNotDefined(JNIEnv *env, jstring key) {
static jmethodID constructor = nullptr;
if (!constructor)
constructor =
env->GetMethodID(keyNotDefinedEx, "<init>", "(Ljava/lang/String;)V");
jobject exception = env->NewObject(keyNotDefinedEx, constructor, key);
env->Throw(static_cast<jthrowable>(exception));
}
extern "C" {
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: containsKey
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_containsKey
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val) return false;
return true;
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getType
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getType
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val) return NT_UNASSIGNED;
return val->type();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putBoolean
* Signature: (Ljava/lang/String;Z)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBoolean
(JNIEnv *env, jclass, jstring key, jboolean value)
{
return nt::SetEntryValue(JavaStringRef(env, key),
nt::Value::MakeBoolean(value != JNI_FALSE));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putDouble
* Signature: (Ljava/lang/String;D)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDouble
(JNIEnv *env, jclass, jstring key, jdouble value)
{
return nt::SetEntryValue(JavaStringRef(env, key),
nt::Value::MakeDouble(value));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putString
* Signature: (Ljava/lang/String;Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putString
(JNIEnv *env, jclass, jstring key, jstring value)
{
return nt::SetEntryValue(JavaStringRef(env, key),
nt::Value::MakeString(JavaStringRef(env, value)));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putRaw
* Signature: (Ljava/lang/String;[B)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2_3B
(JNIEnv *env, jclass, jstring key, jbyteArray value)
{
auto v = FromJavaRaw(env, value);
if (!v) return false;
return nt::SetEntryValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putRaw
* Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
(JNIEnv *env, jclass, jstring key, jobject value, jint len)
{
auto v = FromJavaRawBB(env, value, len);
if (!v) return false;
return nt::SetEntryValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putBooleanArray
* Signature: (Ljava/lang/String;[Z)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBooleanArray
(JNIEnv *env, jclass, jstring key, jbooleanArray value)
{
auto v = FromJavaBooleanArray(env, value);
if (!v) return false;
return nt::SetEntryValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putDoubleArray
* Signature: (Ljava/lang/String;[D)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDoubleArray
(JNIEnv *env, jclass, jstring key, jdoubleArray value)
{
auto v = FromJavaDoubleArray(env, value);
if (!v) return false;
return nt::SetEntryValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: putStringArray
* Signature: (Ljava/lang/String;[Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putStringArray
(JNIEnv *env, jclass, jstring key, jobjectArray value)
{
auto v = FromJavaStringArray(env, value);
if (!v) return false;
return nt::SetEntryValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutBoolean
* Signature: (Ljava/lang/String;Z)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBoolean
(JNIEnv *env, jclass, jstring key, jboolean value)
{
nt::SetEntryTypeValue(JavaStringRef(env, key), nt::Value::MakeBoolean(value != JNI_FALSE));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutDouble
* Signature: (Ljava/lang/String;D)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDouble
(JNIEnv *env, jclass, jstring key, jdouble value)
{
nt::SetEntryTypeValue(JavaStringRef(env, key), nt::Value::MakeDouble(value));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutString
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutString
(JNIEnv *env, jclass, jstring key, jstring value)
{
nt::SetEntryTypeValue(JavaStringRef(env, key),
nt::Value::MakeString(JavaStringRef(env, value)));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutRaw
* Signature: (Ljava/lang/String;[B)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2_3B
(JNIEnv *env, jclass, jstring key, jbyteArray value)
{
auto v = FromJavaRaw(env, value);
if (!v) return;
nt::SetEntryTypeValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutRaw
* Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
(JNIEnv *env, jclass, jstring key, jobject value, jint len)
{
auto v = FromJavaRawBB(env, value, len);
if (!v) return;
nt::SetEntryTypeValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutBooleanArray
* Signature: (Ljava/lang/String;[Z)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBooleanArray
(JNIEnv *env, jclass, jstring key, jbooleanArray value)
{
auto v = FromJavaBooleanArray(env, value);
if (!v) return;
nt::SetEntryTypeValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutDoubleArray
* Signature: (Ljava/lang/String;[D)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDoubleArray
(JNIEnv *env, jclass, jstring key, jdoubleArray value)
{
auto v = FromJavaDoubleArray(env, value);
if (!v) return;
nt::SetEntryTypeValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: forcePutStringArray
* Signature: (Ljava/lang/String;[Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutStringArray
(JNIEnv *env, jclass, jstring key, jobjectArray value)
{
auto v = FromJavaStringArray(env, value);
if (!v) return;
nt::SetEntryTypeValue(JavaStringRef(env, key), v);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getValue
* Signature: (Ljava/lang/String;)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaObject(env, *val);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getBoolean
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsBoolean()) {
ThrowTableKeyNotDefined(env, key);
return false;
}
return val->GetBoolean();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getDouble
* Signature: (Ljava/lang/String;)D
*/
JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsDouble()) {
ThrowTableKeyNotDefined(env, key);
return 0;
}
return val->GetDouble();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsString()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaString(env, val->GetString());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getRaw
* Signature: (Ljava/lang/String;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsRaw()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaByteArray(env, val->GetRaw());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getBooleanArray
* Signature: (Ljava/lang/String;)[Z
*/
JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsBooleanArray()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaBooleanArray(env, val->GetBooleanArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getDoubleArray
* Signature: (Ljava/lang/String;)[D
*/
JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsDoubleArray()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaDoubleArray(env, val->GetDoubleArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getStringArray
* Signature: (Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsStringArray()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaStringArray(env, val->GetStringArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getValue
* Signature: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2Ljava_lang_Object_2
(JNIEnv *env, jclass, jstring key, jobject defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val) return defaultValue;
return ToJavaObject(env, *val);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getBoolean
* Signature: (Ljava/lang/String;Z)Z
*/
JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2Z
(JNIEnv *env, jclass, jstring key, jboolean defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsBoolean()) return defaultValue;
return val->GetBoolean();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getDouble
* Signature: (Ljava/lang/String;D)D
*/
JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2D
(JNIEnv *env, jclass, jstring key, jdouble defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsDouble()) return defaultValue;
return val->GetDouble();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getString
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key, jstring defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsString()) return defaultValue;
return ToJavaString(env, val->GetString());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getRaw
* Signature: (Ljava/lang/String;[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2_3B
(JNIEnv *env, jclass, jstring key, jbyteArray defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsRaw()) return defaultValue;
return ToJavaByteArray(env, val->GetRaw());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getBooleanArray
* Signature: (Ljava/lang/String;[Z)[Z
*/
JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2_3Z
(JNIEnv *env, jclass, jstring key, jbooleanArray defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsBooleanArray()) return defaultValue;
return ToJavaBooleanArray(env, val->GetBooleanArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getDoubleArray
* Signature: (Ljava/lang/String;[D)[D
*/
JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2_3D
(JNIEnv *env, jclass, jstring key, jdoubleArray defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsDoubleArray()) return defaultValue;
return ToJavaDoubleArray(env, val->GetDoubleArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getStringArray
* Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2_3Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key, jobjectArray defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsStringArray()) return defaultValue;
return ToJavaStringArray(env, val->GetStringArray());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: setEntryFlags
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setEntryFlags
(JNIEnv *env, jclass, jstring key, jint flags)
{
nt::SetEntryFlags(JavaStringRef(env, key), flags);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getEntryFlags
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntryFlags
(JNIEnv *env, jclass, jstring key)
{
return nt::GetEntryFlags(JavaStringRef(env, key));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: deleteEntry
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteEntry
(JNIEnv *env, jclass, jstring key)
{
nt::DeleteEntry(JavaStringRef(env, key));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: deleteAllEntries
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteAllEntries
(JNIEnv *, jclass)
{
nt::DeleteAllEntries();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getEntries
* Signature: (Ljava/lang/String;I)[Ledu/wpi/first/wpilibj/networktables/EntryInfo;
*/
JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntries
(JNIEnv *env, jclass, jstring prefix, jint types)
{
auto arr = nt::GetEntryInfo(JavaStringRef(env, prefix), types);
jobjectArray jarr = env->NewObjectArray(arr.size(), entryInfoCls, nullptr);
if (!jarr) return nullptr;
for (size_t i = 0; i < arr.size(); ++i) {
JavaLocal<jobject> jelem(env, ToJavaObject(env, arr[i]));
env->SetObjectArrayElement(jarr, i, jelem);
}
return jarr;
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: flush
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_flush
(JNIEnv *, jclass)
{
nt::Flush();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: addEntryListener
* Signature: (Ljava/lang/String;Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/EntryListenerFunction;Z)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addEntryListener
(JNIEnv *envouter, jclass, jstring prefix, jobject listener, jint flags)
{
// the shared pointer to the weak global will keep it around until the
// entry listener is destroyed
auto listener_global =
std::make_shared<JavaGlobal<jobject>>(envouter, listener);
// cls is a temporary here; cannot be used within callback functor
jclass cls = envouter->GetObjectClass(listener);
if (!cls) return 0;
// method ids, on the other hand, are safe to retain
jmethodID mid = envouter->GetMethodID(
cls, "apply", "(ILjava/lang/String;Ljava/lang/Object;I)V");
if (!mid) return 0;
return nt::AddEntryListener(
JavaStringRef(envouter, prefix),
[=](unsigned int uid, nt::StringRef name,
std::shared_ptr<nt::Value> value, unsigned int flags_) {
JNIEnv *env = listenerEnv;
if (!env || !env->functions) return;
// get the handler
auto handler = listener_global->obj();
// convert the value into the appropriate Java type
jobject jobj = ToJavaObject(env, *value);
if (!jobj) return;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
env->CallVoidMethod(handler, mid, (jint)uid, ToJavaString(env, name),
jobj, (jint)(flags_));
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
},
flags);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: removeEntryListener
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeEntryListener
(JNIEnv *, jclass, jint entryListenerUid)
{
nt::RemoveEntryListener(entryListenerUid);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: addConnectionListener
* Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/ConnectionListenerFunction;Z)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addConnectionListener
(JNIEnv *envouter, jclass, jobject listener, jboolean immediateNotify)
{
// the shared pointer to the weak global will keep it around until the
// entry listener is destroyed
auto listener_global =
std::make_shared<JavaGlobal<jobject>>(envouter, listener);
// cls is a temporary here; cannot be used within callback functor
jclass cls = envouter->GetObjectClass(listener);
if (!cls) return 0;
// method ids, on the other hand, are safe to retain
jmethodID mid = envouter->GetMethodID(
cls, "apply", "(IZLedu/wpi/first/wpilibj/networktables/ConnectionInfo;)V");
if (!mid) return 0;
return nt::AddConnectionListener(
[=](unsigned int uid, bool connected, const nt::ConnectionInfo& conn) {
JNIEnv *env = listenerEnv;
if (!env || !env->functions) return;
// get the handler
auto handler = listener_global->obj();
//if (!handler) goto done; // can happen due to weak reference
// convert into the appropriate Java type
jobject jobj = ToJavaObject(env, conn);
if (!jobj) return;
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
env->CallVoidMethod(handler, mid, (jint)uid,
(jboolean)(connected ? 1 : 0), jobj);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
},
immediateNotify != JNI_FALSE);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: removeConnectionListener
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeConnectionListener
(JNIEnv *, jclass, jint connListenerUid)
{
nt::RemoveConnectionListener(connListenerUid);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getRpc
* Signature: (Ljava/lang/String;)[B
*/
JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2
(JNIEnv *env, jclass, jstring key)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsRpc()) {
ThrowTableKeyNotDefined(env, key);
return nullptr;
}
return ToJavaByteArray(env, val->GetRpc());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getRpc
* Signature: (Ljava/lang/String;[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2_3B
(JNIEnv *env, jclass, jstring key, jbyteArray defaultValue)
{
auto val = nt::GetEntryValue(JavaStringRef(env, key));
if (!val || !val->IsRpc()) return defaultValue;
return ToJavaByteArray(env, val->GetRpc());
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: callRpc
* Signature: (Ljava/lang/String;[B)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2_3B
(JNIEnv *env, jclass, jstring key, jbyteArray params)
{
return nt::CallRpc(JavaStringRef(env, key), JavaByteRef(env, params));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: callRpc
* Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
*/
JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I
(JNIEnv *env, jclass, jstring key, jobject params, jint params_len)
{
return nt::CallRpc(JavaStringRef(env, key),
JavaByteRefBB(env, params, params_len));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: setNetworkIdentity
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setNetworkIdentity
(JNIEnv *env, jclass, jstring name)
{
nt::SetNetworkIdentity(JavaStringRef(env, name));
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: startServer
* Signature: (Ljava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startServer
(JNIEnv *env, jclass, jstring persistFilename, jstring listenAddress,
jint port)
{
nt::StartServer(JavaStringRef(env, persistFilename),
JavaStringRef(env, listenAddress).c_str(), port);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: stopServer
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopServer
(JNIEnv *, jclass)
{
nt::StopServer();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: startClient
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient
(JNIEnv *env, jclass, jstring serverName, jint port)
{
nt::StartClient(JavaStringRef(env, serverName).c_str(), port);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: stopClient
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopClient
(JNIEnv *, jclass)
{
nt::StopClient();
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: setUpdateRate
* Signature: (D)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setUpdateRate
(JNIEnv *, jclass, jdouble interval)
{
nt::SetUpdateRate(interval);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: getConnections
* Signature: ()[Ledu/wpi/first/wpilibj/networktables/ConnectionInfo;
*/
JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getConnections
(JNIEnv *env, jclass)
{
auto arr = nt::GetConnections();
jobjectArray jarr =
env->NewObjectArray(arr.size(), connectionInfoCls, nullptr);
if (!jarr) return nullptr;
for (size_t i = 0; i < arr.size(); ++i) {
JavaLocal<jobject> jelem(env, ToJavaObject(env, arr[i]));
env->SetObjectArrayElement(jarr, i, jelem);
}
return jarr;
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: savePersistent
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_savePersistent
(JNIEnv *env, jclass, jstring filename)
{
const char *err = nt::SavePersistent(JavaStringRef(env, filename));
if (err) env->ThrowNew(persistentEx, err);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: loadPersistent
* Signature: (Ljava/lang/String;)[Ljava/lang/String;
*/
JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_loadPersistent
(JNIEnv *env, jclass, jstring filename)
{
std::vector<std::string> warns;
const char *err = nt::LoadPersistent(JavaStringRef(env, filename),
[&](size_t line, const char *msg) {
std::ostringstream oss;
oss << line << ": " << msg;
warns.push_back(oss.str());
});
if (err) {
env->ThrowNew(persistentEx, err);
return nullptr;
}
return ToJavaStringArray(env, warns);
}
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: now
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_now
(JNIEnv *, jclass)
{
return nt::Now();
}
} // extern "C"
// Thread where log callbacks are actually performed.
//
// 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.
class LoggerThreadJNI {
public:
static LoggerThreadJNI& GetInstance() {
ATOMIC_STATIC(LoggerThreadJNI, instance);
return instance;
}
LoggerThreadJNI();
~LoggerThreadJNI();
void SetFunc(JNIEnv* env, jobject func, jmethodID mid);
void Start();
void Stop();
void Log(unsigned int level, const char* file, unsigned int line,
const char* msg);
private:
void ThreadMain();
std::thread m_thread;
std::mutex m_mutex;
std::condition_variable m_cond;
std::atomic_bool m_active;
struct LogMessage {
LogMessage(unsigned int level_, const char* file_, unsigned int line_,
const char* msg_)
: level(level_), file(file_), line(line_), msg(msg_) {}
unsigned int level;
const char* file;
unsigned int line;
std::string msg;
};
std::queue<LogMessage> m_queue;
std::mutex m_shutdown_mutex;
std::condition_variable m_shutdown_cv;
bool m_shutdown = false;
jobject m_func = nullptr;
jmethodID m_mid;
ATOMIC_STATIC_DECL(LoggerThreadJNI)
};
ATOMIC_STATIC_INIT(LoggerThreadJNI)
LoggerThreadJNI::LoggerThreadJNI() {
m_active = false;
}
LoggerThreadJNI::~LoggerThreadJNI() {
Stop();
}
void LoggerThreadJNI::SetFunc(JNIEnv* env, jobject func, jmethodID mid) {
std::lock_guard<std::mutex> lock(m_mutex);
// free global reference
if (m_func) env->DeleteGlobalRef(m_func);
// create global reference
m_func = env->NewGlobalRef(func);
m_mid = mid;
}
void LoggerThreadJNI::Start() {
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_active) return;
m_active = true;
}
{
std::lock_guard<std::mutex> lock(m_shutdown_mutex);
m_shutdown = false;
}
m_thread = std::thread(&LoggerThreadJNI::ThreadMain, this);
}
void LoggerThreadJNI::Stop() {
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_active) return;
m_active = false;
}
m_cond.notify_one(); // wake up thread
// join threads, with timeout
if (m_thread.joinable()) {
std::unique_lock<std::mutex> lock(m_shutdown_mutex);
auto timeout_time =
std::chrono::steady_clock::now() + std::chrono::seconds(1);
if (m_shutdown_cv.wait_until(lock, timeout_time,
[&] { return m_shutdown; }))
m_thread.join();
else
m_thread.detach(); // timed out, detach it
}
}
void LoggerThreadJNI::Log(unsigned int level, const char *file,
unsigned int line, const char *msg) {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_active) return;
m_queue.emplace(level, file, line, msg);
m_cond.notify_one();
}
void LoggerThreadJNI::ThreadMain() {
JNIEnv *env;
jint rs = jvm->AttachCurrentThread((void**)&env, NULL);
if (rs != JNI_OK) return;
std::unique_lock<std::mutex> 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
env->CallVoidMethod(func, mid, (jint)item.level,
ToJavaString(env, item.file), (jint)item.line,
ToJavaString(env, item.msg));
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
lock.lock();
}
}
if (jvm) jvm->DetachCurrentThread();
// use condition variable to signal thread shutdown
{
std::lock_guard<std::mutex> lock(m_shutdown_mutex);
m_shutdown = true;
m_shutdown_cv.notify_one();
}
}
extern "C" {
/*
* Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI
* Method: setLogger
* Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/LoggerFunction;I)V
*/
JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setLogger
(JNIEnv *env, jclass, jobject func, jint minLevel)
{
// cls is a temporary here; cannot be used within callback functor
jclass cls = env->GetObjectClass(func);
if (!cls) return;
// method ids, on the other hand, are safe to retain
jmethodID mid = env->GetMethodID(
cls, "apply", "(ILjava/lang/String;ILjava/lang/String;)V");
if (!mid) return;
auto& thread = LoggerThreadJNI::GetInstance();
thread.SetFunc(env, func, mid);
thread.Start();
nt::SetLogger(
[](unsigned int level, const char *file, unsigned int line,
const char *msg) {
LoggerThreadJNI::GetInstance().Log(level, file, line, msg);
},
minLevel);
}
} // extern "C"