diff --git a/CMakeLists.txt b/CMakeLists.txt index d8c47f1851..4512af415f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,40 @@ fix_default_compiler_settings_() file(GLOB_RECURSE SRC_FILES src/*.cpp) include_directories(include src) + +# Java bindings +if (NOT WITHOUT_JAVA) + find_package(Java) + find_package(JNI) + include(UseJava) + set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked") + + include_directories(${JNI_INCLUDE_DIRS}) + list(APPEND SRC_FILES java/lib/NetworkTablesJNI.cpp) + + file(GLOB_RECURSE JAVA_SOURCES java/src/*.java) + set(CMAKE_JNI_TARGET true) + add_jar(networktables ${JAVA_SOURCES}) + + # Generate JNI headers + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h" + DEPENDS networktables java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java + COMMAND "${Java_JAVAH_EXECUTABLE}" + -jni + -o "${CMAKE_CURRENT_BINARY_DIR}/edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h" + -classpath "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/networktables.dir/" + edu.wpi.first.wpilibj.networktables.NetworkTablesJNI + ) + include_directories("${CMAKE_CURRENT_BINARY_DIR}") + set_source_files_properties( + java/lib/NetworkTablesJNI.cpp + OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h" + ) + + #add_subdirectory(java/test) +endif() + if (WIN32) add_library(ntcore SHARED ${SRC_FILES} ntcore.def) else() diff --git a/build.gradle b/build.gradle index e1b9400974..76aafaa5c6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,17 @@ import org.apache.tools.ant.taskdefs.condition.Os +if (!project.hasProperty('skipJava')) { +apply plugin: 'java' +} apply plugin: 'cpp' apply plugin: 'visual-studio' +def armjdkDownloadSite = 'http://www.oracle.com/technetwork/java/javase/downloads/jdk8-arm-downloads-2187472.html' +def armjdkFolder = 'jdk-linux-arm-vfp-sflt' +def armjdkVersion = 'jdk1.8.0_33' +def armjdkLocation = System.getProperty("user.home") + File.separator + armjdkFolder + File.separator + armjdkVersion +def generatedJNIHeaderLoc = 'build/include' + model { toolChains { gcc(Gcc) { @@ -38,9 +47,11 @@ model { // deprecated function, remove this line (this will cause calling deprecated functions // to be treated as a warning rather than an error). args << '-Wno-error=deprecated-declarations' + args << '-m32' } linker.withArguments { args -> args << '-rdynamic' + args << '-m32' } } target('x64') { @@ -95,14 +106,55 @@ model { targetPlatform 'arm' targetPlatform 'x86' targetPlatform 'x64' + binaries.all { + tasks.withType(CppCompile) { + if (!project.hasProperty('skipJava')) { + if (targetPlatform == platforms.arm) { + dependsOn verifyArmJre + + // JDK is included for jni.h. We also need the arm-linux specific headers. + // This does not need to change when compiling on Windows + // The JNI headers are put into the build/include directory by the jniHeaders task + cppCompiler.args '-I', "${armjdkLocation}/include" + cppCompiler.args '-I', "${armjdkLocation}/include/linux" + } else { + def jdkLocation = org.gradle.internal.jvm.Jvm.current().javaHome + cppCompiler.args '-I', "${jdkLocation}/include" + + if (targetPlatform.operatingSystem.macOsX) { + cppCompiler.args '-I', "${jdkLocation}/include/darwin" + } else if (targetPlatform.operatingSystem.linux) { + cppCompiler.args '-I', "${jdkLocation}/include/linux" + } else if (targetPlatform.operatingSystem.windows) { + cppCompiler.args '-I', "${jdkLocation}/include/win32" + } else if (targetPlatform.operatingSystem.freeBSD) { + cppCompiler.args '-I', "${jdkLocation}/include/freebsd" + } + } + + jniHeadersNetworkTables.outputs.files.each { file -> + cppCompiler.args '-I', file.getPath() + } + dependsOn jniHeadersNetworkTables + } + } + } sources { cpp { source { srcDirs = ["src"] + if (!project.hasProperty('skipJava')) { + srcDirs "java/lib" + } includes = ["**/*.cpp"] } exportedHeaders { srcDirs = ["include"] + if (!project.hasProperty('skipJava')) { + jniHeadersNetworkTables.outputs.files.each { file -> + srcDirs file.getPath() + } + } includes = ["**/*.h"] } } @@ -110,3 +162,97 @@ model { } } } + +if (!project.hasProperty('skipJava')) { + +compileJava { + options.compilerArgs << "-Xlint:unchecked" +} + +sourceSets { + main { + java { + srcDirs = ["java/src"] + } + } +} + +jar { + description = 'Generates NetworkTables jar, with the JNI shared libraries embedded' + dependsOn { armNtcoreSharedLibrary } + dependsOn { x64NtcoreSharedLibrary } + dependsOn { x86NtcoreSharedLibrary } + dependsOn { classes } + binaries.withType(SharedLibraryBinary) { binary -> + from(file(binary.sharedLibraryFile)) { + if (binary.targetPlatform == platforms.arm) { + into "Linux/arm" + } else if (binary.targetPlatform.operatingSystem.name == "Linux") { + if (binary.targetPlatform.architecture.name == "x86-64") { + into "Linux/amd64" + } else { + into "Linux/" + binary.targetPlatform.architecture.name + } + } else { + into binary.targetPlatform.operatingSystem.name + "/" + binary.targetPlatform.architecture.name + } + } + } +} + +task networktablesJavaSource(type: Jar, dependsOn: classes) { + description = 'Generates the source jar for NetworkTables java' + group = 'WPILib' + classifier = 'classes' + from sourceSets.main.allJava +} + +task networktablesJavadoc(type: Jar, dependsOn: javadoc) { + description = 'Generates the javadoc jar for NetworkTables java' + group = 'WPILib' + classifier = 'javadoc' + from javadoc.destinationDir +} + +/** + * Generates the JNI headers + */ +task jniHeadersNetworkTables { + description = 'Generates JNI headers from edu.wpi.first.wpilibj.networktables.*' + group = 'WPILib' + def outputFolder = file(generatedJNIHeaderLoc) + inputs.files sourceSets.main.output + outputs.file outputFolder + doLast { + outputFolder.mkdirs() + exec { + executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') + args '-d', outputFolder + args '-classpath', sourceSets.main.output.classesDir + args 'edu.wpi.first.wpilibj.networktables.NetworkTablesJNI' + } + } +} + +clean { + delete generatedJNIHeaderLoc +} + +// Ensures that the ARM JNI headers have been downloaded and are in the correct location for generating the JNI build +task verifyArmJre { + description = 'Verifies that the ARM JDK is downloaded in the user directory' + group = 'WPILib' + def outputFolder = new File(armjdkLocation) + outputs.file outputFolder + doLast { + if (!outputFolder.exists() && !outputFolder.isDirectory()) { + def errorMessage = 'The ARM JDK was not found. Please install the JDK in the following location:' + + System.lineSeparator() + armjdkLocation + + System.lineSeparator() + 'You can download the JDK here:' + + System.lineSeparator() + armjdkDownloadSite + throw new GradleException(errorMessage) + } + } +} + +} // skipJava diff --git a/java/lib/NetworkTablesJNI.cpp b/java/lib/NetworkTablesJNI.cpp new file mode 100644 index 0000000000..9b7f7edc4d --- /dev/null +++ b/java/lib/NetworkTablesJNI.cpp @@ -0,0 +1,1178 @@ +#include +#include +#include + +#include "edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h" +#include "ntcore.h" + +// +// Globals and load/unload +// + +// Used for callback. +static JavaVM *jvm; +static jclass booleanCls = nullptr; +static jclass doubleCls = nullptr; +static jclass stringCls = nullptr; +static jclass connectionInfoCls = nullptr; +static jclass keyNotDefinedEx = nullptr; +static jclass persistentEx = nullptr; + +jint 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/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); + + return JNI_VERSION_1_6; +} + +void 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 (keyNotDefinedEx) env->DeleteGlobalRef(keyNotDefinedEx); + if (persistentEx) env->DeleteGlobalRef(persistentEx); +} + +// +// 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() { + JNIEnv *env; + if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != + JNI_OK) + 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() { + JNIEnv *env; + if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != + JNI_OK) + 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; +}; + +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 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); +} + +// +// 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)); +} + +/* + * 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)); +} + +/* + * 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 + (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: 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)); +} + +/* + * 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 + (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: 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: 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: 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: 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, + 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", "(ILjava/lang/String;Ljava/lang/Object;Z)V"); + if (!mid) return 0; + + return nt::AddEntryListener( + JavaStringRef(envouter, prefix), + [=](unsigned int uid, nt::StringRef name, + std::shared_ptr value, bool is_new) { + // need to attach as we're coming from a separate thread here + JNIEnv *env; + if (jvm->AttachCurrentThread(reinterpret_cast(&env), + nullptr) != JNI_OK) + return; + + // get the handler + auto handler = listener_global->obj(env); + if (!handler) { + // can happen due to weak reference + jvm->DetachCurrentThread(); + return; + } + + // convert the value into the appropriate Java type + jobject jobj = ToJavaObject(env, *value); + + if (!jobj) { + jvm->DetachCurrentThread(); + return; + } + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + jvm->DetachCurrentThread(); + return; + } + env->CallVoidMethod(handler, mid, (jint)uid, ToJavaString(env, name), + jobj, (jboolean)(is_new ? 1 : 0)); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + + jvm->DetachCurrentThread(); + }, + immediateNotify); +} + +/* + * 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) { + // need to attach as we're coming from a separate thread here + JNIEnv *env; + if (jvm->AttachCurrentThread(reinterpret_cast(&env), + nullptr) != JNI_OK) + return; + + // get the handler + auto handler = listener_global->obj(env); + if (!handler) { + // can happen due to weak reference + jvm->DetachCurrentThread(); + return; + } + + // convert into the appropriate Java type + jobject jobj = ToJavaObject(env, conn); + + if (!jobj) { + jvm->DetachCurrentThread(); + return; + } + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + jvm->DetachCurrentThread(); + return; + } + env->CallVoidMethod(handler, mid, (jint)uid, + (jboolean)(connected ? 1 : 0), jobj); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + + jvm->DetachCurrentThread(); + }, + immediateNotify); +} + +/* + * 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 + (JNIEnv *env, jclass, jstring key, jbyteArray params) +{ + return nt::CallRpc(JavaStringRef(env, key), JavaByteRef(env, params)); +} + +/* + * 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(); +} + +/* + * 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 *envouter, jclass, jobject func, jint minLevel) +{ + // the shared pointer to the global will keep it around until the + // a new logger is set + auto func_global = std::make_shared>(envouter, func); + + // cls is a temporary here; cannot be used within callback functor + jclass cls = envouter->GetObjectClass(func); + if (!cls) return; + + // method ids, on the other hand, are safe to retain + jmethodID mid = envouter->GetMethodID( + cls, "apply", "(ILjava/lang/String;ILjava/lang/String;)V"); + if (!mid) return; + + return nt::SetLogger( + [=](unsigned int level, const char *file, unsigned int line, + const char *msg) { + // need to attach as we're coming from a separate thread here + JNIEnv *env; + if (jvm->AttachCurrentThread(reinterpret_cast(&env), + nullptr) != JNI_OK) + return; + + // get the handler + auto handler = func_global->obj(); + if (!handler) { + // shouldn't happen, but ignore if it does + jvm->DetachCurrentThread(); + return; + } + + env->CallVoidMethod(handler, mid, (jint)level, ToJavaString(env, file), + (jint)line, ToJavaString(env, msg)); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + + jvm->DetachCurrentThread(); + }, + minLevel); +} diff --git a/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java b/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java new file mode 100644 index 0000000000..d9f0e1d6a8 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java @@ -0,0 +1,17 @@ +package edu.wpi.first.wpilibj.networktables; + +public class ConnectionInfo { + final String remote_id; + final String remote_name; + final int remote_port; + final long last_update; + final int protocol_version; + + ConnectionInfo(String remote_id, String remote_name, int remote_port, long last_update, int protocol_version) { + this.remote_id = remote_id; + this.remote_name = remote_name; + this.remote_port = remote_port; + this.last_update = last_update; + this.protocol_version = protocol_version; + } +} diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java new file mode 100644 index 0000000000..5a06ebed66 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTable.java @@ -0,0 +1,735 @@ +package edu.wpi.first.wpilibj.networktables; + +import edu.wpi.first.wpilibj.tables.*; +import java.io.*; +import java.util.*; + +/** + * A network table that knows its subtable path. + * + */ +public class NetworkTable implements ITable, IRemote { + /** + * The path separator for sub-tables and keys + * + */ + public static final char PATH_SEPARATOR = '/'; + /** + * The default port that network tables operates on + */ + public static final int DEFAULT_PORT = 1735; + + private static boolean client = false; + private static boolean running = false; + private static int port = DEFAULT_PORT; + private static String ipAddress = null; + private static String persistentFilename = "networktables.ini"; + + private synchronized static void checkInit() { + if (running) + throw new IllegalStateException( + "Network tables has already been initialized"); + } + + /** + * initializes network tables + */ + public synchronized static void initialize() { + checkInit(); + if (client) + NetworkTablesJNI.startClient(ipAddress, port); + else + NetworkTablesJNI.startServer(persistentFilename, ipAddress, port); + running = true; + } + + /** + * set that network tables should be a server + * This must be called before initalize or getTable + */ + public synchronized static void setServerMode() { + checkInit(); + client = false; + } + + /** + * set that network tables should be a client + * This must be called before initalize or getTable + */ + public synchronized static void setClientMode() { + checkInit(); + client = true; + } + + /** + * set the team the robot is configured for (this will set the ip address that + * network tables will connect to in client mode) + * This must be called before initalize or getTable + * @param team the team number + */ + public synchronized static void setTeam(int team) { + setIPAddress("10." + (team / 100) + "." + (team % 100) + ".2"); + } + + /** + * @param address the adress that network tables will connect to in client + * mode + */ + public synchronized static void setIPAddress(final String address) { + checkInit(); + ipAddress = address; + } + + /** + * @param port the port number that network tables will connect to in client + * mode or listen to in server mode + */ + public synchronized static void setPort(int aport) { + checkInit(); + port = aport; + } + + /** + * Gets the table with the specified key. If the table does not exist, a new + *table will be created.
+ * This will automatically initialize network tables if it has not been + *already + * + * @param key + * the key name + * @return the network table requested + */ + public synchronized static NetworkTable getTable(String key) { + if (!running) + initialize(); + if (key.isEmpty()) + return new NetworkTable(key); + return new NetworkTable(PATH_SEPARATOR + key); + } + + private final String path; + + NetworkTable(String path) { + this.path = path; + } + public String toString() { return "NetworkTable: " + path; } + + public boolean isConnected() { + ConnectionInfo[] conns = NetworkTablesJNI.getConnections(); + return conns.length > 0; + } + + public boolean isServer() { + return !client; + } + + private class ListenerBase { + public int uid; + } + + private class ConnectionListenerAdapter extends ListenerBase implements NetworkTablesJNI.ConnectionListenerFunction { + private final IRemote targetSource; + private final IRemoteConnectionListener targetListener; + + public ConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener targetListener) { + this.targetSource = targetSource; + this.targetListener = targetListener; + } + + public void apply(int uid, boolean connected, ConnectionInfo conn) { + if (connected) + targetListener.connected(targetSource); + else + targetListener.disconnected(targetSource); + } + } + + private final Hashtable connectionListenerMap = new Hashtable(); + public synchronized void addConnectionListener(IRemoteConnectionListener listener, + boolean immediateNotify) { + ConnectionListenerAdapter adapter = connectionListenerMap.get(listener); + if (adapter != null) + throw new IllegalStateException("Cannot add the same listener twice"); + adapter = new ConnectionListenerAdapter(this, listener); + adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify); + connectionListenerMap.put(listener, adapter); + } + + public synchronized void removeConnectionListener(IRemoteConnectionListener listener) { + ConnectionListenerAdapter adapter = connectionListenerMap.get(listener); + if (adapter != null) { + NetworkTablesJNI.removeConnectionListener(adapter.uid); + connectionListenerMap.remove(listener); + } + } + + public void addTableListener(ITableListener listener) { + addTableListener(listener, false); + } + + private class TableListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + private final int prefixLen; + private final ITable targetSource; + private final ITableListener targetListener; + + public TableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { + this.prefixLen = prefixLen; + this.targetSource = targetSource; + this.targetListener = targetListener; + } + + public void apply(int uid, String key, Object value, boolean isNew) { + String relativeKey = key.substring(prefixLen); + if (relativeKey.indexOf(PATH_SEPARATOR) != -1) + return; + targetListener.valueChanged(targetSource, relativeKey, value, isNew); + } + } + + private final Hashtable> listenerMap = new Hashtable>(); + public synchronized void addTableListener(ITableListener listener, + boolean immediateNotify) { + List adapters = listenerMap.get(listener); + if (adapters == null) { + adapters = new ArrayList(); + listenerMap.put(listener, adapters); + } + TableListenerAdapter adapter = + new TableListenerAdapter(path.length() + 1, this, listener); + adapter.uid = NetworkTablesJNI.addEntryListener(path + PATH_SEPARATOR, adapter, immediateNotify); + adapters.add(adapter); + } + + private class KeyListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + private final String relativeKey; + private final String fullKey; + private final ITable targetSource; + private final ITableListener targetListener; + + public KeyListenerAdapter(String relativeKey, String fullKey, ITable targetSource, ITableListener targetListener) { + this.relativeKey = relativeKey; + this.fullKey = fullKey; + this.targetSource = targetSource; + this.targetListener = targetListener; + } + + public void apply(int uid, String key, Object value, boolean isNew) { + if (!key.equals(fullKey)) + return; + targetListener.valueChanged(targetSource, relativeKey, value, isNew); + } + } + + public synchronized void addTableListener(String key, ITableListener listener, + boolean immediateNotify) { + List adapters = listenerMap.get(listener); + if (adapters == null) { + adapters = new ArrayList(); + listenerMap.put(listener, adapters); + } + String fullKey = path + PATH_SEPARATOR + key; + KeyListenerAdapter adapter = + new KeyListenerAdapter(key, fullKey, this, listener); + adapter.uid = NetworkTablesJNI.addEntryListener(fullKey, adapter, immediateNotify); + adapters.add(adapter); + } + + private class SubListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + private final int prefixLen; + private final ITable targetSource; + private final ITableListener targetListener; + private final Set notifiedTables = new HashSet(); + + public SubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { + this.prefixLen = prefixLen; + this.targetSource = targetSource; + this.targetListener = targetListener; + } + + public void apply(int uid, String key, Object value, boolean isNew) { + String relativeKey = key.substring(prefixLen); + int endSubTable = relativeKey.indexOf(PATH_SEPARATOR); + if (endSubTable == -1) + return; + String subTableKey = relativeKey.substring(0, endSubTable); + if (notifiedTables.contains(subTableKey)) + return; + notifiedTables.add(subTableKey); + targetListener.valueChanged(targetSource, subTableKey, targetSource.getSubTable(subTableKey), true); + } + } + + public synchronized void addSubTableListener(final ITableListener listener) { + List adapters = listenerMap.get(listener); + if (adapters == null) { + adapters = new ArrayList(); + listenerMap.put(listener, adapters); + } + SubListenerAdapter adapter = + new SubListenerAdapter(path.length() + 1, this, listener); + adapter.uid = NetworkTablesJNI.addEntryListener(path + PATH_SEPARATOR, adapter, true); + adapters.add(adapter); + } + + public synchronized void removeTableListener(ITableListener listener) { + List adapters = listenerMap.get(listener); + if (adapters != null) { + for (int i = 0; i < adapters.size(); ++i) + NetworkTablesJNI.removeEntryListener(adapters.get(i).uid); + adapters.clear(); + } + } + + /** + * Returns the table at the specified key. If there is no table at the + * specified key, it will create a new table + * + * @param key + * the key name + * @return the networktable to be returned + */ + public ITable getSubTable(String key) { + return new NetworkTable(path + PATH_SEPARATOR + key); + } + + /** + * Checks the table and tells if it contains the specified key + * + * @param key + * the key to be checked + */ + public boolean containsKey(String key) { + return NetworkTablesJNI.containsKey(path + PATH_SEPARATOR + key); + } + + public boolean containsSubTable(String key) { + String subtablePrefix = path + key + PATH_SEPARATOR; + //List keys = node.getEntryStore().keys(); + //for (int i = 0; i < keys.size(); ++i) { + // if (((String)keys.get(i)).startsWith(subtablePrefix)) + // return true; + //} + return false; + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putNumber(String key, double value) { + NetworkTablesJNI.putDouble(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public double getNumber(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getDouble(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public double getNumber(String key, double defaultValue) { + return NetworkTablesJNI.getDouble(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putString(String key, String value) { + NetworkTablesJNI.putString(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public String getString(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getString(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public String getString(String key, String defaultValue) { + return NetworkTablesJNI.getString(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putBoolean(String key, boolean value) { + NetworkTablesJNI.putBoolean(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public boolean getBoolean(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getBoolean(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public boolean getBoolean(String key, boolean defaultValue) { + return NetworkTablesJNI.getBoolean(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putBooleanArray(String key, boolean[] value) { + NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getBooleanArray(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public boolean[] getBooleanArray(String key, boolean[] defaultValue) { + return NetworkTablesJNI.getBooleanArray(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putNumberArray(String key, double[] value) { + NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public double[] getNumberArray(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getDoubleArray(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public double[] getNumberArray(String key, double[] defaultValue) { + return NetworkTablesJNI.getDoubleArray(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key + * the key + * @param value + * the value + */ + public void putStringArray(String key, String[] value) { + NetworkTablesJNI.putStringArray(path + PATH_SEPARATOR + key, value); + } + + /** + * Returns the key that the name maps to. + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public String[] getStringArray(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getStringArray(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public String[] getStringArray(String key, String[] defaultValue) { + return NetworkTablesJNI.getStringArray(path + PATH_SEPARATOR + key, defaultValue); + } + + /** + * Maps the specified key to the specified value in this table. The key can + * not be null. The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key the key name + * @param value the value to be put + */ + public void putValue(String key, Object value) { + if (value instanceof Boolean) + NetworkTablesJNI.putBoolean(path + PATH_SEPARATOR + key, ((Boolean)value).booleanValue()); + else if (value instanceof Double) + NetworkTablesJNI.putDouble(path + PATH_SEPARATOR + key, ((Double)value).doubleValue()); + else if (value instanceof String) + NetworkTablesJNI.putString(path + PATH_SEPARATOR + key, (String)value); + else if (value instanceof byte[]) + NetworkTablesJNI.putRaw(path + PATH_SEPARATOR + key, (byte[])value); + else if (value instanceof boolean[]) + NetworkTablesJNI.putBooleanArray(path + PATH_SEPARATOR + key, (boolean[])value); + else if (value instanceof double[]) + NetworkTablesJNI.putDoubleArray(path + PATH_SEPARATOR + key, (double[])value); + else if (value instanceof String[]) + NetworkTablesJNI.putStringArray(path + PATH_SEPARATOR + key, (String[])value); + } + + /** + * Returns the key that the name maps to. + * NOTE: If the value is a double, it will return a Double object, + * not a primitive. To get the primitive, use getDouble + * + * @param key + * the key name + * @return the key + * @throws TableKeyNotDefinedException + * if the specified key is null + */ + public Object getValue(String key) throws TableKeyNotDefinedException { + return NetworkTablesJNI.getValue(path + PATH_SEPARATOR + key); + } + + /** + * Returns the key that the name maps to. If the key is null, it will return + * the default value + * NOTE: If the value is a double, it will return a Double object, + * not a primitive. To get the primitive, use getDouble + * + * @param key + * the key name + * @param defaultValue + * the default value if the key is null + * @return the key + */ + public Object getValue(String key, Object defaultValue) { + return NetworkTablesJNI.getValue(path + PATH_SEPARATOR + key, defaultValue); + } + + /** The persistent flag value. */ + public static final int PERSISTENT = 1; + + /** + * Sets flags on the specified key in this table. The key can + * not be null. + * + * @param key the key name + * @param flags the flags to set + */ + public void setFlags(String key, int flags) { + NetworkTablesJNI.setEntryFlags(path + PATH_SEPARATOR + key, flags); + } + + /** + * Returns the flags for the specified key. + * + * @param key + * the key name + * @return the flags, or 0 if the key is not defined + */ + public int getFlags(String key) { + return NetworkTablesJNI.getEntryFlags(path + PATH_SEPARATOR + key); + } + + /* + * Deprecated Methods + */ + /** + * @deprecated + * Maps the specified key to the specified value in this table. + * The key can not be null. + * The value can be retrieved by calling the get method with a key that is + * equal to the original key. + * @param key the key + * @param value the value + * @throws IllegalArgumentException if key is null + */ + public void putInt(String key, int value) { putNumber(key, value); } + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @return the value + * @throws TableKeyNotDefinedException if there is no value mapped to by the + * key + * @throws IllegalArgumentException if the value mapped to by the key is not + * an int + * @throws IllegalArgumentException if the key is null + */ + public int getInt(String key) throws TableKeyNotDefinedException { + return (int)getNumber(key); + } + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @param defaultValue the value returned if the key is undefined + * @return the value + * @throws NetworkTableKeyNotDefined if there is no value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not + * an int + * @throws IllegalArgumentException if the key is null + */ + public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException { + try { + return (int)getNumber(key); + } catch (NoSuchElementException ex) { + return defaultValue; + } + } + + /** + * @deprecated + * Maps the specified key to the specified value in this table. + * The key can not be null. + * The value can be retrieved by calling the get method with a key that is + * equal to the original key. + * @param key the key + * @param value the value + * @throws IllegalArgumentException if key is null + */ + public void putDouble(String key, double value) { + putNumber(key, value); + } + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @return the value + * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no + * value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not a + * double + * @throws IllegalArgumentException if the key is null + */ + public double getDouble(String key) throws TableKeyNotDefinedException { + return getNumber(key); + } + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @param defaultValue the value returned if the key is undefined + * @return the value + * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no + * value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not a + * double + * @throws IllegalArgumentException if the key is null + */ + public double getDouble(String key, double defaultValue) { + return getNumber(key, defaultValue); + } +} diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java new file mode 100644 index 0000000000..d654cfce58 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTableKeyNotDefined.java @@ -0,0 +1,22 @@ +package edu.wpi.first.wpilibj.networktables; + +import edu.wpi.first.wpilibj.tables.TableKeyNotDefinedException; + +/** + * An exception throw when the lookup a a key-value fails in a {@link NetworkTable} + * + * @deprecated to provide backwards compatability for new api + * + * @author Mitchell + * + */ +public class NetworkTableKeyNotDefined extends TableKeyNotDefinedException { + + /** + * @param key the key that was not defined in the table + */ + public NetworkTableKeyNotDefined(String key) { + super(key); + } + +} diff --git a/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java new file mode 100644 index 0000000000..45dabc35a1 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java @@ -0,0 +1,145 @@ +package edu.wpi.first.wpilibj.networktables; + +import edu.wpi.first.wpilibj.tables.*; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class NetworkTablesJNI { + static boolean libraryLoaded = false; + static File jniLibrary = null; + static { + if (!libraryLoaded) { + try { + String osname = System.getProperty("os.name"); + String resname = "/" + osname + "/" + System.getProperty("os.arch") + "/"; + System.out.println("platform: " + resname); + if (osname.startsWith("Windows")) + resname += "ntcore.dll"; + else + resname += "libntcore.so"; + InputStream is = NetworkTablesJNI.class.getResourceAsStream(resname); + if (is != null) { + // create temporary file + if (System.getProperty("os.name").startsWith("Windows")) + jniLibrary = File.createTempFile("NetworkTablesJNI", ".dll"); + else + jniLibrary = File.createTempFile("libNetworkTablesJNI", ".so"); + // flag for delete on exit + jniLibrary.deleteOnExit(); + OutputStream os = new FileOutputStream(jniLibrary); + + byte[] buffer = new byte[1024]; + int readBytes; + try { + while ((readBytes = is.read(buffer)) != -1) { + os.write(buffer, 0, readBytes); + } + } finally { + os.close(); + is.close(); + } + + System.load(jniLibrary.getAbsolutePath()); + } else { + if (osname.startsWith("Windows")) + System.loadLibrary("ntcore.dll"); + else + System.loadLibrary("libntcore.so"); + } + } catch (IOException ex) { + ex.printStackTrace(); + System.exit(1); + } + libraryLoaded = true; + } + } + + public static native boolean containsKey(String key); + public static native int getType(String key); + + public static native boolean putBoolean(String key, boolean value); + public static native boolean putDouble(String key, double value); + public static native boolean putString(String key, String value); + public static native boolean putRaw(String key, byte[] value); + public static native boolean putBooleanArray(String key, boolean[] value); + public static native boolean putDoubleArray(String key, double[] value); + public static native boolean putStringArray(String key, String[] value); + + public static native void forcePutBoolean(String key, boolean value); + public static native void forcePutDouble(String key, double value); + public static native void forcePutString(String key, String value); + public static native void forcePutRaw(String key, byte[] value); + public static native void forcePutBooleanArray(String key, boolean[] value); + public static native void forcePutDoubleArray(String key, double[] value); + public static native void forcePutStringArray(String key, String[] value); + + public static native Object getValue(String key) throws TableKeyNotDefinedException; + public static native boolean getBoolean(String key) throws TableKeyNotDefinedException; + public static native double getDouble(String key) throws TableKeyNotDefinedException; + public static native String getString(String key) throws TableKeyNotDefinedException; + public static native byte[] getRaw(String key) throws TableKeyNotDefinedException; + public static native boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException; + public static native double[] getDoubleArray(String key) throws TableKeyNotDefinedException; + public static native String[] getStringArray(String key) throws TableKeyNotDefinedException; + + public static native Object getValue(String key, Object defaultValue); + public static native boolean getBoolean(String key, boolean defaultValue); + public static native double getDouble(String key, double defaultValue); + public static native String getString(String key, String defaultValue); + public static native byte[] getRaw(String key, byte[] defaultValue); + public static native boolean[] getBooleanArray(String key, boolean[] defaultValue); + public static native double[] getDoubleArray(String key, double[] defaultValue); + public static native String[] getStringArray(String key, String[] defaultValue); + + public static native void setEntryFlags(String key, int flags); + public static native int getEntryFlags(String key); + + public static native void deleteEntry(String key); + public static native void deleteAllEntries(); + + // public static native EntryInfo[] getEntryInfo(String prefix, int types); + + public static native void flush(); + + public interface EntryListenerFunction { + void apply(int uid, String key, Object value, boolean isNew); + } + public static native int addEntryListener(String prefix, EntryListenerFunction listener, boolean immediateNotify); + public static native void removeEntryListener(int entryListenerUid); + + public interface ConnectionListenerFunction { + void apply(int uid, boolean connected, ConnectionInfo conn); + } + public static native int addConnectionListener(ConnectionListenerFunction listener, boolean immediateNotify); + public static native void removeConnectionListener(int connListenerUid); + + // public static native void createRpc(String key, byte[] def, IRpc rpc); + public static native byte[] getRpc(String key) throws TableKeyNotDefinedException; + public static native byte[] getRpc(String key, byte[] defaultValue); + public static native int callRpc(String key, byte[] params); + // public static native byte[] getRpcResultBlocking(int callUid); + // public static native byte[] getRpcResultNonblocking(int callUid) throws RpcNoResponseException; + + public static native void setNetworkIdentity(String name); + public static native void startServer(String persistFilename, String listenAddress, int port); + public static native void stopServer(); + public static native void startClient(String serverName, int port); + public static native void stopClient(); + public static native void setUpdateRate(double interval); + + public static native ConnectionInfo[] getConnections(); + + public static native void savePersistent(String filename) throws PersistentException; + public static native String[] loadPersistent(String filename) throws PersistentException; // returns warnings + + public static native long now(); + + public interface LoggerFunction { + void apply(int level, String file, int line, String msg); + } + public static native void setLogger(LoggerFunction func, int minLevel); +} diff --git a/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java b/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java new file mode 100644 index 0000000000..8d521fbbb0 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/networktables/PersistentException.java @@ -0,0 +1,18 @@ +package edu.wpi.first.wpilibj.networktables; + +import java.io.IOException; + +/** + * An exception thrown when persistent load/save fails in a {@link NetworkTable} + * + */ +public class PersistentException extends IOException { + + /** + * @param message The error message + */ + public PersistentException(String message) { + super(message); + } + +} diff --git a/java/src/edu/wpi/first/wpilibj/tables/IRemote.java b/java/src/edu/wpi/first/wpilibj/tables/IRemote.java new file mode 100644 index 0000000000..a79f740473 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/tables/IRemote.java @@ -0,0 +1,37 @@ +package edu.wpi.first.wpilibj.tables; + + +/** + * Represents an object that has a remote connection + * + * @author Mitchell + * + */ +public interface IRemote { + /** + * Register an object to listen for connection and disconnection events + * + * @param listener the listener to be register + * @param immediateNotify if the listener object should be notified of the current connection state + */ + public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify); + + /** + * Unregister a listener from connection events + * + * @param listener the listener to be unregistered + */ + public void removeConnectionListener(IRemoteConnectionListener listener); + + /** + * Get the current state of the objects connection + * @return the current connection state + */ + public boolean isConnected(); + + /** + * If the object is acting as a server + * @return if the object is a server + */ + public boolean isServer(); +} diff --git a/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java b/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java new file mode 100644 index 0000000000..cab9b1c3ec --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java @@ -0,0 +1,20 @@ +package edu.wpi.first.wpilibj.tables; + +/** + * A listener that listens for connection changes in a {@link IRemote} object + * + * @author Mitchell + * + */ +public interface IRemoteConnectionListener { + /** + * Called when an IRemote is connected + * @param remote the object that connected + */ + public void connected(IRemote remote); + /** + * Called when an IRemote is disconnected + * @param remote the object that disconnected + */ + public void disconnected(IRemote remote); +} diff --git a/java/src/edu/wpi/first/wpilibj/tables/ITable.java b/java/src/edu/wpi/first/wpilibj/tables/ITable.java new file mode 100644 index 0000000000..122ddcc8e9 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/tables/ITable.java @@ -0,0 +1,292 @@ +package edu.wpi.first.wpilibj.tables; + +import java.util.NoSuchElementException; + + +/** + * A table whose values can be read and written to + * + * @author Mitchell + * + */ +public interface ITable { + + /** + * @param key the key to search for + * @return true if the table as a value assigned to the given key + */ + public boolean containsKey(String key); + + /** + * @param key the key to search for + * @return true if there is a subtable with the key which contains at least + * one key/subtable of its own + */ + public boolean containsSubTable(String key); + + /** + * @param key the name of the table relative to this one + * @return a sub table relative to this one + */ + public ITable getSubTable(String key); + + /** + * Gets the value associated with a key as an object + * @param key the key of the value to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public Object getValue(String key) throws TableKeyNotDefinedException; + + /** + * Put a value in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + * @throws IllegalArgumentException when the value is not supported by the + * table + */ + public void putValue(String key, Object value) + throws IllegalArgumentException; + + /** + * Put a number in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putNumber(String key, double value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public double getNumber(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public double getNumber(String key, double defaultValue); + + /** + * Put a string in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putString(String key, String value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public String getString(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public String getString(String key, String defaultValue); + + /** + * Put a boolean in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putBoolean(String key, boolean value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public boolean getBoolean(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public boolean getBoolean(String key, boolean defaultValue); + + /** + * Put a boolean array in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putBooleanArray(String key, boolean[] value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public boolean[] getBooleanArray(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public boolean[] getBooleanArray(String key, boolean[] defaultValue); + + /** + * Put a number array in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putNumberArray(String key, double[] value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public double[] getNumberArray(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public double[] getNumberArray(String key, double[] defaultValue); + + /** + * Put a string array in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + */ + public void putStringArray(String key, String[] value); + /** + * @param key the key to look up + * @return the value associated with the given key + * @throws TableKeyNotDefinedException if there is no value associated with + * the given key + */ + public String[] getStringArray(String key) throws TableKeyNotDefinedException; + /** + * @param key the key to look up + * @param defaultValue the value to be returned if no value is found + * @return the value associated with the given key or the given default value + * if there is no value associated with the key + */ + public String[] getStringArray(String key, String[] defaultValue); + + /** + * Add a listener for changes to the table + * @param listener the listener to add + */ + public void addTableListener(ITableListener listener); + /** + * Add a listener for changes to the table + * @param listener the listener to add + * @param immediateNotify if true then this listener will be notified of all + * current entries (marked as new) + */ + public void addTableListener(ITableListener listener, + boolean immediateNotify); + + /** + * Add a listener for changes to a specific key the table + * @param key the key to listen for + * @param listener the listener to add + * @param immediateNotify if true then this listener will be notified of all + * current entries (marked as new) + */ + public void addTableListener(String key, ITableListener listener, + boolean immediateNotify); + /** + * This will immediately notify the listener of all current sub tables + * @param listener + */ + public void addSubTableListener(final ITableListener listener); + /** + * Remove a listener from receiving table events + * @param listener the listener to be removed + */ + public void removeTableListener(ITableListener listener); + + /* + * Depricated Methods + */ + /** + * @deprecated + * Maps the specified key to the specified value in this table. + * The key can not be null. + * The value can be retrieved by calling the get method with a key that is + * equal to the original key. + * @param key the key + * @param value the value + * @throws IllegalArgumentException if key is null + */ + public void putInt(String key, int value); + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @return the value + * @throws TableKeyNotDefinedException if there is no value mapped to by the + * key + * @throws IllegalArgumentException if the value mapped to by the key is not + * an int + * @throws IllegalArgumentException if the key is null + */ + public int getInt(String key) throws TableKeyNotDefinedException; + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @param defaultValue the value returned if the key is undefined + * @return the value + * @throws NetworkTableKeyNotDefined if there is no value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not + * an int + * @throws IllegalArgumentException if the key is null + */ + public int getInt(String key, int defaultValue) + throws TableKeyNotDefinedException; + + /** + * @deprecated + * Maps the specified key to the specified value in this table. + * The key can not be null. + * The value can be retrieved by calling the get method with a key that is + * equal to the original key. + * @param key the key + * @param value the value + * @throws IllegalArgumentException if key is null + */ + public void putDouble(String key, double value); + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @return the value + * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no + * value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not a + * double + * @throws IllegalArgumentException if the key is null + */ + public double getDouble(String key) throws TableKeyNotDefinedException; + + /** + * @deprecated + * Returns the value at the specified key. + * @param key the key + * @param defaultValue the value returned if the key is undefined + * @return the value + * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no + * value mapped to by the key + * @throws IllegalArgumentException if the value mapped to by the key is not a + * double + * @throws IllegalArgumentException if the key is null + */ + public double getDouble(String key, double defaultValue); +} diff --git a/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java b/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java new file mode 100644 index 0000000000..f53528f180 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/tables/ITableListener.java @@ -0,0 +1,19 @@ +package edu.wpi.first.wpilibj.tables; + +/** + * A listener that listens to changes in values in a {@link ITable} + * + * @author Mitchell + * + */ +public interface ITableListener { + /** + * Called when a key-value pair is changed in a {@link ITable} + * WARNING: If a new key-value is put in this method value changed will immediatly be called which could lead to recursive code + * @param source the table the key-value pair exists in + * @param key the key associated with the value that changed + * @param value the new value + * @param isNew true if the key did not previously exist in the table, otherwise it is false + */ + public void valueChanged(ITable source, String key, Object value, boolean isNew); +} diff --git a/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java b/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java new file mode 100644 index 0000000000..4ca3a54189 --- /dev/null +++ b/java/src/edu/wpi/first/wpilibj/tables/TableKeyNotDefinedException.java @@ -0,0 +1,20 @@ +package edu.wpi.first.wpilibj.tables; + +import java.util.NoSuchElementException; + +/** + * An exception throw when the lookup a a key-value fails in a {@link ITable} + * + * @author Mitchell + * + */ +public class TableKeyNotDefinedException extends NoSuchElementException { + + /** + * @param key the key that was not defined in the table + */ + public TableKeyNotDefinedException(String key) { + super("Unknown Table Key: "+key); + } + +} diff --git a/java/test/Client.java b/java/test/Client.java new file mode 100644 index 0000000000..7f18690ca7 --- /dev/null +++ b/java/test/Client.java @@ -0,0 +1,39 @@ +import edu.wpi.first.wpilibj.networktables.*; +import edu.wpi.first.wpilibj.tables.*; + +public class Client { + private static class MyLogger implements NetworkTablesJNI.LoggerFunction { + public void apply(int level, String file, int line, String msg) { + System.err.println(msg); + } + } + + public static void main(String[] args) { + NetworkTablesJNI.setLogger(new MyLogger(), 0); + NetworkTable.setIPAddress("127.0.0.1"); + NetworkTable.setPort(10000); + NetworkTable.setClientMode(); + NetworkTable nt = NetworkTable.getTable(""); + try { Thread.sleep(2000); } catch (InterruptedException e) {} + try { + System.out.println("Got foo: " + nt.getNumber("foo")); + } catch(TableKeyNotDefinedException ex) { + } + nt.putBoolean("bar", false); + nt.setFlags("bar", NetworkTable.PERSISTENT); + nt.putBoolean("bar2", true); + nt.putBoolean("bar2", false); + nt.putBoolean("bar2", true); + nt.putString("str", "hello world"); + double[] nums = new double[3]; + nums[0] = 0.5; + nums[1] = 1.2; + nums[2] = 3.0; + nt.putNumberArray("numarray", nums); + String[] strs = new String[2]; + strs[0] = "Hello"; + strs[1] = "World"; + nt.putStringArray("strarray", strs); + try { Thread.sleep(10000); } catch (InterruptedException e) {} + } +} diff --git a/java/test/Server.java b/java/test/Server.java new file mode 100644 index 0000000000..33844fb92e --- /dev/null +++ b/java/test/Server.java @@ -0,0 +1,26 @@ +import edu.wpi.first.wpilibj.networktables.*; +import edu.wpi.first.wpilibj.tables.*; + +public class Server { + private static class MyLogger implements NetworkTablesJNI.LoggerFunction { + public void apply(int level, String file, int line, String msg) { + System.err.println(msg); + } + } + + public static void main(String[] args) { + NetworkTablesJNI.setLogger(new MyLogger(), 0); + NetworkTable.setIPAddress("127.0.0.1"); + NetworkTable.setPort(10000); + NetworkTable.setServerMode(); + NetworkTable nt = NetworkTable.getTable(""); + try { Thread.sleep(1000); } catch (InterruptedException e) {} + nt.putNumber("foo", 0.5); + nt.setFlags("foo", NetworkTable.PERSISTENT); + nt.putNumber("foo2", 0.5); + nt.putNumber("foo2", 0.7); + nt.putNumber("foo2", 0.6); + nt.putNumber("foo2", 0.5); + try { Thread.sleep(10000); } catch (InterruptedException e) {} + } +} diff --git a/ntcore.def b/ntcore.def index f370f139cc..97bbbd1a92 100644 --- a/ntcore.def +++ b/ntcore.def @@ -41,3 +41,62 @@ NT_PackRpcValues @42 NT_UnpackRpcValues @43 NT_DisposeRpcDefinition @44 NT_DisposeRpcCallInfo @45 + +; JNI functions +JNI_OnLoad +JNI_OnUnload +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_containsKey +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getType +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBoolean +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDouble +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putString +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBooleanArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDoubleArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putStringArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBoolean +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDouble +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutString +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBooleanArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDoubleArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutStringArray +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue__Ljava_lang_String_2Ljava_lang_Object_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean__Ljava_lang_String_2Z +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble__Ljava_lang_String_2D +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString__Ljava_lang_String_2Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw__Ljava_lang_String_2_3B +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray__Ljava_lang_String_2_3Z +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray__Ljava_lang_String_2_3D +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray__Ljava_lang_String_2_3Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setEntryFlags +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntryFlags +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteEntry +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteAllEntries +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_flush +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addEntryListener +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeEntryListener +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addConnectionListener +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeConnectionListener +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2 +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc__Ljava_lang_String_2_3B +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setNetworkIdentity +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startServer +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopServer +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopClient +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setUpdateRate +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getConnections +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_savePersistent +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_loadPersistent +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_now +Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setLogger