#include #include #include #include #include #include #include #include #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(&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(&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(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(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(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(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(env->NewGlobalRef(local)); if (!entryInfoCls) return JNI_ERR; env->DeleteLocalRef(local); local = env->FindClass("edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined"); keyNotDefinedEx = static_cast(env->NewGlobalRef(local)); if (!keyNotDefinedEx) return JNI_ERR; env->DeleteLocalRef(local); local = env->FindClass("edu/wpi/first/wpilibj/networktables/PersistentException"); persistentEx = static_cast(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(&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 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 class JavaGlobal { public: JavaGlobal(JNIEnv *env, T obj) : m_obj(static_cast(env->NewGlobalRef(obj))) {} ~JavaGlobal() { if (!jvm || nt::NotifierDestroyed()) return; JNIEnv *env; if (jvm->AttachCurrentThread(reinterpret_cast(&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 class JavaWeakGlobal { public: JavaWeakGlobal(JNIEnv *env, T obj) : m_obj(static_cast(env->NewWeakGlobalRef(obj))) {} ~JavaWeakGlobal() { if (!jvm || nt::NotifierDestroyed()) return; JNIEnv *env; if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != JNI_OK) return; if (!env || !env->functions) return; env->DeleteWeakGlobalRef(m_obj); jvm->DetachCurrentThread(); } JavaLocal obj(JNIEnv *env) { return JavaLocal(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(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(m_elements), m_size); } private: JNIEnv *m_env; void *m_elements; size_t m_size; }; std::shared_ptr FromJavaRaw(JNIEnv *env, jbyteArray jarr) { size_t len = env->GetArrayLength(jarr); jbyte *elements = static_cast(env->GetPrimitiveArrayCritical(jarr, nullptr)); if (!elements) return nullptr; auto rv = nt::Value::MakeRaw( nt::StringRef(reinterpret_cast(elements), len)); env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT); return rv; } std::shared_ptr 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(elements), len)); return rv; } std::shared_ptr FromJavaRpc(JNIEnv *env, jbyteArray jarr) { size_t len = env->GetArrayLength(jarr); jbyte *elements = static_cast(env->GetPrimitiveArrayCritical(jarr, nullptr)); if (!elements) return nullptr; auto rv = nt::Value::MakeRpc( nt::StringRef(reinterpret_cast(elements), len)); env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT); return rv; } std::shared_ptr FromJavaBooleanArray(JNIEnv *env, jbooleanArray jarr) { size_t len = env->GetArrayLength(jarr); std::vector arr; arr.reserve(len); jboolean *elements = static_cast(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 FromJavaDoubleArray(JNIEnv *env, jdoubleArray jarr) { size_t len = env->GetArrayLength(jarr); jdouble *elements = static_cast(env->GetPrimitiveArrayCritical(jarr, nullptr)); if (!elements) return nullptr; auto rv = nt::Value::MakeDoubleArray(nt::ArrayRef(elements, len)); env->ReleasePrimitiveArrayCritical(jarr, elements, JNI_ABORT); return rv; } std::shared_ptr FromJavaStringArray(JNIEnv *env, jobjectArray jarr) { size_t len = env->GetArrayLength(jarr); std::vector arr; arr.reserve(len); for (size_t i = 0; i < len; ++i) { JavaLocal elem( env, static_cast(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(str.data())); return jarr; } static jbooleanArray ToJavaBooleanArray(JNIEnv *env, nt::ArrayRef arr) { jbooleanArray jarr = env->NewBooleanArray(arr.size()); if (!jarr) return nullptr; jboolean *elements = static_cast(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 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 arr) { jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr); if (!jarr) return nullptr; for (size_t i = 0; i < arr.size(); ++i) { JavaLocal 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, "", "(Z)V"); if (!doubleConstructor) doubleConstructor = env->GetMethodID(doubleCls, "", "(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, "", "(Ljava/lang/String;Ljava/lang/String;IJI)V"); JavaLocal remote_id(env, ToJavaString(env, info.remote_id)); JavaLocal 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, "", "(Ljava/lang/String;IIJ)V"); JavaLocal 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, "", "(Ljava/lang/String;)V"); jobject exception = env->NewObject(keyNotDefinedEx, constructor, key); env->Throw(static_cast(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 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>(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 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>(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 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 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 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() { m_active = false; } LoggerThreadJNI::~LoggerThreadJNI() { Stop(); } void LoggerThreadJNI::SetFunc(JNIEnv* env, jobject func, jmethodID mid) { std::lock_guard 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 lock(m_mutex); if (m_active) return; m_active = true; } { std::lock_guard lock(m_shutdown_mutex); m_shutdown = false; } m_thread = std::thread(&LoggerThreadJNI::ThreadMain, this); } void LoggerThreadJNI::Stop() { { std::lock_guard 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 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 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 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 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"