From: Martin Ling Date: Sun, 27 Apr 2014 08:48:18 +0000 (+0100) Subject: Add Java bindings. X-Git-Tag: libsigrok-0.4.0~1250 X-Git-Url: http://sigrok.org/gitweb/?p=libsigrok.git;a=commitdiff_plain;h=9fcf4d0b5b9969b0b7770e71994b960f15f7757f Add Java bindings. --- diff --git a/.gitignore b/.gitignore index 0f192d25..8af73ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,13 @@ bindings/python/sigrok/core/lowlevel_wrap.c bindings/python/sigrok/core/classes.py bindings/python/sigrok/core/classes_wrap.cpp +# Files generated by building Java bindings +*.class +bindings/java/org/sigrok/core/classes/*.java +bindings/java/org/sigrok/core/classes/classes_wrap.cxx +bindings/java/libsigrok_java_core_classes.so +bindings/java/sigrok-core.jar + # Files generated by the testsuite test-suite.log tests/*.log diff --git a/Makefile.am b/Makefile.am index e535b517..01e002cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -427,6 +427,50 @@ CLEAN_EXTRA += python-clean endif +if BINDINGS_JAVA + +JDIR = bindings/java +JPKG = org/sigrok/core/classes +JINT = $(JDIR)/$(JPKG)/classes.i +JSRC = $(JDIR)/$(JPKG)/classes_wrap.cxx +JLIB = $(JDIR)/libsigrok_java_core_classes.so +JJAR = $(JDIR)/sigrok-core.jar + +java-build: $(JJAR) $(JLIB) + +$(JSRC): $(JINT) bindings/swig/classes.i + swig -c++ -java -package org.sigrok.core.classes \ + -Iinclude -Ibindings/cxx/include \ + -outdir $(JDIR)/$(JPKG) $(JINT) + +$(JJAR): $(JSRC) $(JDIR)/$(JPKG)/*.java + $(JAVAC) -sourcepath $(JDIR) $(JDIR)/$(JPKG)/*.java + jar cf $(JJAR) -C $(JDIR) $(JPKG) + +$(JLIB): $(JSRC) bindings/cxx/libsigrokxx.la + $(CXX) $(CXXFLAGS) -L.libs -Lbindings/cxx/.libs \ + -fno-strict-aliasing -fPIC -shared \ + $(JDIR)/$(JPKG)/classes_wrap.cxx -lsigrokxx \ + -o $(JLIB) + +java-install: + $(INSTALL) -d $(libdir)/jni + $(INSTALL) $(JLIB) -t $(libdir)/jni + $(INSTALL) -d $(datadir)/java + $(INSTALL) $(JJAR) -t $(datadir)/java + +java-clean: + rm $(JSRC) + rm $(JDIR)/$(JPKG)/*.class + rm $(JJAR) + rm $(JLIB) + +BUILD_EXTRA += java-build +INSTALL_EXTRA += java-install +CLEAN_EXTRA += java-clean + +endif + all-local: $(BUILD_EXTRA) install-exec-local: $(INSTALL_EXTRA) clean-extra: $(CLEAN_EXTRA) diff --git a/bindings/java/org/sigrok/core/classes/DatafeedCallback.java b/bindings/java/org/sigrok/core/classes/DatafeedCallback.java new file mode 100644 index 00000000..82d1171f --- /dev/null +++ b/bindings/java/org/sigrok/core/classes/DatafeedCallback.java @@ -0,0 +1,6 @@ +package org.sigrok.core.classes; + +public interface DatafeedCallback +{ + public void run(Device device, Packet packet); +} diff --git a/bindings/java/org/sigrok/core/classes/LogCallback.java b/bindings/java/org/sigrok/core/classes/LogCallback.java new file mode 100644 index 00000000..4a09ab29 --- /dev/null +++ b/bindings/java/org/sigrok/core/classes/LogCallback.java @@ -0,0 +1,6 @@ +package org.sigrok.core.classes; + +public interface LogCallback +{ + public void run(LogLevel loglevel, String message); +} diff --git a/bindings/java/org/sigrok/core/classes/SourceCallback.java b/bindings/java/org/sigrok/core/classes/SourceCallback.java new file mode 100644 index 00000000..53f59e3e --- /dev/null +++ b/bindings/java/org/sigrok/core/classes/SourceCallback.java @@ -0,0 +1,6 @@ +package org.sigrok.core.classes; + +public interface SourceCallback +{ + public void run(int revents); +} diff --git a/bindings/java/org/sigrok/core/classes/classes.i b/bindings/java/org/sigrok/core/classes/classes.i new file mode 100644 index 00000000..20e55d57 --- /dev/null +++ b/bindings/java/org/sigrok/core/classes/classes.i @@ -0,0 +1,325 @@ +%module classes + +/* Automatically load JNI library. */ +%pragma(java) jniclasscode=%{ + static { + System.loadLibrary("sigrok_java_core_classes"); + } +%} + +/* Map Java FileDescriptor objects to int fds */ +%typemap(jni) int fd "jobject" +%typemap(jtype) int fd "java.io.FileDescriptor" +%typemap(jstype) int fd "java.io.FileDescriptor" +%typemap(javain) int fd "$javainput" + +%typemap(in) int fd { + jclass FileDescriptor = jenv->FindClass("java/io/FileDescriptor"); + jfieldID fd = jenv->GetFieldID(FileDescriptor, "fd", "I"); + $1 = jenv->GetIntField($input, fd); +} + +/* Map Glib::VariantBase to a Variant class in Java */ +%rename(Variant) VariantBase; +namespace Glib { + class VariantBase {}; +} + +/* Map between std::vector and java.util.Vector */ +%define VECTOR(CValue, JValue) + +%typemap(jni) std::vector< CValue > "jobject" +%typemap(jtype) std::vector< CValue > "java.util.Vector" +%typemap(jstype) std::vector< CValue > "java.util.Vector" + +%typemap(javain, + pre=" $javaclassname temp$javainput = $javaclassname.convertVector($javainput);", + pgcppname="temp$javainput") + std::vector< CValue > "$javaclassname.getCPtr(temp$javainput)" + +%typemap(javacode) std::vector< CValue > %{ + static $javaclassname convertVector(java.util.Vector in) + { + $javaclassname out = new $javaclassname(); + for (JValue value : in) + out.add(value); + return out; + } +%} + +%typemap(javaout) std::vector< CValue > { + return (java.util.Vector)$jnicall; +} + +%typemap(out) std::vector< CValue > { + jclass Vector = jenv->FindClass("java/util/Vector"); + jmethodID Vector_init = jenv->GetMethodID(Vector, "", "()V"); + jmethodID Vector_add = jenv->GetMethodID(Vector, "add", + "(Ljava/lang/Object;)Z"); + jclass Value = jenv->FindClass("org/sigrok/core/classes/" #JValue); + jmethodID Value_init = jenv->GetMethodID(Value, "", "(JZ)V"); + $result = jenv->NewObject(Vector, Vector_init); + jlong value; + for (auto entry : $1) + { + *(CValue **) &value = new CValue(entry); + jenv->CallObjectMethod($result, Vector_add, + jenv->NewObject(Value, Value_init, value, true)); + } +} + +%enddef + +VECTOR(std::shared_ptr, Channel) +VECTOR(std::shared_ptr, HardwareDevice) + +/* Common macro for mapping between std::map and java.util.Map */ + +%define MAP_COMMON(CKey, CValue, JKey, JValue) + +%typemap(jstype) std::map< CKey, CValue > + "java.util.Map" + +%typemap(javain, + pre=" $javaclassname temp$javainput = $javaclassname.convertMap($javainput);", + pgcppname="temp$javainput") + std::map< CKey, CValue > "$javaclassname.getCPtr(temp$javainput)" + +%typemap(javacode) std::map< CKey, CValue > %{ + static $javaclassname convertMap(java.util.Map in) + { + $javaclassname out = new $javaclassname(); + for (java.util.Map.Entry entry : in.entrySet()) + out.set(entry.getKey(), entry.getValue()); + return out; + } +%} + +%typemap(javaout) std::map< CKey, CValue > { + return (java.util.Map)$jnicall; +} + +%enddef + +/* Specialisation for string->string maps. */ + +MAP_COMMON(std::string, std::string, String, String) + +%typemap(out) std::map { + jclass HashMap = jenv->FindClass("java/util/HashMap"); + jmethodID init = jenv->GetMethodID(HashMap, "", "()V"); + jmethodID put = jenv->GetMethodID(HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + $result = jenv->NewObject(HashMap, init); + for (auto entry : $1) + jenv->CallObjectMethod($result, put, + jenv->NewStringUTF(entry.first.c_str()), + jenv->NewStringUTF(entry.second.c_str())); +} + +/* Specialisation macro for string->shared_ptr maps. */ + +%define STRING_TO_SHARED_PTR_MAP(ClassName) + +%typemap(jni) std::map > + "jobject" +%typemap(jtype) std::map > + "java.util.Map" + +MAP_COMMON(std::string, std::shared_ptr, String, ClassName) + +%typemap(out) std::map > { + jclass HashMap = jenv->FindClass("java/util/HashMap"); + jmethodID HashMap_init = jenv->GetMethodID(HashMap, "", "()V"); + jmethodID HashMap_put = jenv->GetMethodID(HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jclass Value = jenv->FindClass("org/sigrok/core/classes/" #ClassName); + jmethodID Value_init = jenv->GetMethodID(Value, "", "(JZ)V"); + $result = jenv->NewObject(HashMap, HashMap_init); + jlong value; + for (auto entry : $1) + { + *(std::shared_ptr< sigrok::ClassName > **)&value = + new std::shared_ptr< sigrok::ClassName>(entry.second); + jenv->CallObjectMethod($result, HashMap_put, + jenv->NewStringUTF(entry.first.c_str()), + jenv->NewObject(Value, Value_init, value, true)); + } +} + +%enddef + +STRING_TO_SHARED_PTR_MAP(Driver) +STRING_TO_SHARED_PTR_MAP(InputFormat) +STRING_TO_SHARED_PTR_MAP(OutputFormat) + +/* Specialisation for ConfigKey->Variant maps */ + +MAP_COMMON(const sigrok::ConfigKey *, Glib::VariantBase, ConfigKey, Variant) + +%typemap(out) std::map { + jclass HashMap = jenv->FindClass("java/util/HashMap"); + jmethodID HashMap_init = jenv->GetMethodID(HashMap, "", "()V"); + jmethodID HashMap_put = jenv->GetMethodID(HashMap, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jclass ConfigKey = jenv->FindClass("org/sigrok/core/classes/ConfigKey"); + jmethodID ConfigKey_init = jenv->GetMethodID(ConfigKey, "", "(JZ)V"); + jclass Variant = jenv->FindClass("org/sigrok/core/classes/Variant"); + jmethodID Variant_init = jenv->GetMethodID(Variant, "", "(JZ)V"); + $result = jenv->NewObject(HashMap, HashMap_init); + jlong key; + jlong value; + for (auto entry : $1) + { + *(const sigrok::ConfigKey **) &key = entry.first; + *(Glib::VariantBase **) &value = new Glib::VariantBase(entry.second); + jenv->CallObjectMethod($result, HashMap_put, + jenv->NewObject(ConfigKey, ConfigKey_init, key, false)); + jenv->NewObject(Variant, Variant_init, value, true)); + } +} + +/* Support Driver.scan() with no arguments. */ +%extend sigrok::Driver { + std::vector > scan() + { + std::map options; + return $self->scan(options); + } +} + +/* Support OutputFormat.create_output(device) with no options. */ +%extend sigrok::OutputFormat { + std::shared_ptr create_output( + std::shared_ptr device) + { + std::map options; + return $self->create_output(device, options); + } +} + +/* Pass JNIEnv parameter to C++ extension methods requiring it. */ + +%typemap(in, numinputs=0) JNIEnv * %{ + $1 = jenv; +%} + +/* Support Java log callbacks. */ + +%inline { +typedef jobject jlogcallback; +} + +%typemap(jni) jlogcallback "jlogcallback" +%typemap(jtype) jlogcallback "LogCallback" +%typemap(jstype) jlogcallback "LogCallback" +%typemap(javain) jlogcallback "$javainput" + +%extend sigrok::Context +{ + void add_log_callback(JNIEnv *env, jlogcallback obj) + { + jclass obj_class = env->GetObjectClass(obj); + jmethodID method = env->GetMethodID(obj_class, "run", + "(Lorg/sigrok/core/classes/LogLevel;Ljava/lang/String;)V"); + jclass LogLevel = (jclass) env->NewGlobalRef( + env->FindClass("org/sigrok/core/classes/LogLevel")); + jmethodID LogLevel_init = env->GetMethodID(LogLevel, "", "(JZ)V"); + jobject obj_ref = env->NewGlobalRef(obj); + + $self->set_log_callback([=] ( + const sigrok::LogLevel *loglevel, + std::string message) + { + jlong loglevel_addr; + *(const sigrok::LogLevel **) &loglevel_addr = loglevel; + jobject loglevel_obj = env->NewObject( + LogLevel, LogLevel_init, loglevel_addr, false); + jobject message_obj = env->NewStringUTF(message.c_str()); + env->CallVoidMethod(obj_ref, method, loglevel_obj, message_obj); + if (env->ExceptionCheck()) + throw sigrok::Error(SR_ERR); + }); + } +} + +/* Support Java datafeed callbacks. */ + +%inline { +typedef jobject jdatafeedcallback; +} + +%typemap(jni) jdatafeedcallback "jdatafeedcallback" +%typemap(jtype) jdatafeedcallback "DatafeedCallback" +%typemap(jstype) jdatafeedcallback "DatafeedCallback" +%typemap(javain) jdatafeedcallback "$javainput" + +%extend sigrok::Session +{ + void add_datafeed_callback(JNIEnv *env, jdatafeedcallback obj) + { + jclass obj_class = env->GetObjectClass(obj); + jmethodID method = env->GetMethodID(obj_class, "run", + "(Lorg/sigrok/core/classes/Device;Lorg/sigrok/core/classes/Packet;)V"); + jclass Device = (jclass) env->NewGlobalRef( + env->FindClass("org/sigrok/core/classes/Device")); + jmethodID Device_init = env->GetMethodID(Device, "", "(JZ)V"); + jclass Packet = (jclass) env->NewGlobalRef( + env->FindClass("org/sigrok/core/classes/Packet")); + jmethodID Packet_init = env->GetMethodID(Packet, "", "(JZ)V"); + jobject obj_ref = env->NewGlobalRef(obj); + + $self->add_datafeed_callback([=] ( + std::shared_ptr device, + std::shared_ptr packet) + { + jlong device_addr; + jlong packet_addr; + *(std::shared_ptr **) &device_addr = + new std::shared_ptr(device); + *(std::shared_ptr **) &packet_addr = + new std::shared_ptr(packet); + jobject device_obj = env->NewObject( + Device, Device_init, device_addr, true); + jobject packet_obj = env->NewObject( + Packet, Packet_init, packet_addr, true); + env->CallVoidMethod(obj_ref, method, device_obj, packet_obj); + if (env->ExceptionCheck()) + throw sigrok::Error(SR_ERR); + }); + } +} + +/* Support Java event source callbacks. */ + +%inline { +typedef jobject jsourcecallback; +} + +%typemap(jni) jsourcecallback "jsourcecallback" +%typemap(jtype) jsourcecallback "SourceCallback" +%typemap(jstype) jsourcecallback "SourceCallback" +%typemap(javain) jsourcecallback "$javainput" + +%extend sigrok::EventSource +{ + std::shared_ptr create( + int fd, Glib::IOCondition events, int timeout, + JNIEnv *env, jsourcecallback obj) + { + (void) $self; + jclass obj_class = env->GetObjectClass(obj); + jmethodID method = env->GetMethodID(obj_class, "run", "(I)V"); + jobject obj_ref = env->NewGlobalRef(obj); + + return sigrok::EventSource::create(fd, events, timeout, [=] (int revents) + { + bool result = env->CallBooleanMethod(obj_ref, method, revents); + if (env->ExceptionCheck()) + throw sigrok::Error(SR_ERR); + return result; + }); + } +} + +%include "bindings/swig/classes.i" diff --git a/configure.ac b/configure.ac index 90b9657a..eac88f1f 100644 --- a/configure.ac +++ b/configure.ac @@ -166,6 +166,11 @@ AC_ARG_ENABLE(python, [build Python bindings [default=yes]]), [BINDINGS_PYTHON="$enableval"], [BINDINGS_PYTHON="yes"]) +AC_ARG_ENABLE(java, + AC_HELP_STRING([--enable-java], + [build Java bindings [default=yes]]), + [BINDINGS_PYTHON="$enableval"], [BINDINGS_JAVA="yes"]) + # Check if the C++ compiler supports the C++11 standard. AX_CXX_COMPILE_STDCXX_11(,[optional]) @@ -174,10 +179,11 @@ if test "x$HAVE_CXX11" != "x1"; then BINDINGS_CXX="no" fi -# Python bindings depend on C++ bindings. +# Python and Java bindings depend on C++ bindings. if test "x$BINDINGS_CXX" != "xyes"; then BINDINGS_PYTHON="no" + BINDINGS_JAVA="no" fi # Checks for libraries. @@ -362,6 +368,20 @@ AC_SUBST(SR_PKGLIBS) CFLAGS="$CFLAGS -I./include/libsigrok $LIB_CFLAGS" CXXFLAGS="$CXXFLAGS -I./include -I./bindings/cxx/include $LIB_CFLAGS" +# Find Java compiler and JNI includes for Java bindings. + +AC_CHECK_PROG([HAVE_JAVAC], [javac], [yes], [no]) + +if test "x$HAVE_JAVAC" = "xyes"; then + AX_PROG_JAVAC + AX_JNI_INCLUDE_DIR + for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS; do + CXXFLAGS="$CXXFLAGS -I$JNI_INCLUDE_DIR" + done +else + BINDINGS_JAVA="no" +fi + # Now set AM_CONDITIONALs and AC_DEFINEs for the enabled/disabled drivers. AM_CONDITIONAL(HW_AGILENT_DMM, test x$HW_AGILENT_DMM = xyes) @@ -553,6 +573,8 @@ AM_CONDITIONAL(BINDINGS_CXX, test x$BINDINGS_CXX = xyes) AM_CONDITIONAL(BINDINGS_PYTHON, test x$BINDINGS_PYTHON = xyes) +AM_CONDITIONAL(BINDINGS_JAVA, test x$BINDINGS_JAVA = xyes) + # Checks for header files. # These are already checked: inttypes.h stdint.h stdlib.h string.h unistd.h. @@ -609,4 +631,5 @@ echo -e "\nEnabled hardware drivers:\n${driver_summary}" echo -e "\nEnabled language bindings:\n" echo " - C++............................. $BINDINGS_CXX" echo " - Python.......................... $BINDINGS_PYTHON" +echo " - Java............................ $BINDINGS_JAVA" echo