Files
allwpilib/java/lib/NetworkTablesJNI.cpp
Peter Johnson 32001427d4 Java: call JNI AttachCurrentThread less frequently.
Each call to AttachCurrentThread results in a new Java thread object being
created.  This is inefficient and also causes debugging issues with Eclipse
due to constant creation and removal of threads.  Now AttachCurrentThread is
only called once for (all) listeners and once for logging (if used).
2015-12-20 20:42:09 -08:00

1449 lines
45 KiB
C++

#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_env(env), m_elements(env->GetDirectBufferAddress(bb)), m_size(len) {}
operator nt::StringRef() const {
return nt::StringRef(reinterpret_cast<char *>(m_elements), m_size);
}
private:
JNIEnv *m_env;
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();
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{false};
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)
};
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"