From a65105d890de477b9e0b1dcb9009d7313e070a5e Mon Sep 17 00:00:00 2001 From: Nicholas Corgan Date: Mon, 2 Sep 2019 14:38:35 -0500 Subject: [PATCH] Added support for BufferChunk conversion * Java Proxy -> Object conversion supports subclasses --- CMakeLists.txt | 1 + FrameworkTypes.cpp | 150 +++++++++++++++++++++++++++++++++++++++++++++ JavaHandle.cpp | 51 +++++++++++++-- JavaProxy.cpp | 33 ++++++++++ JavaProxy.hpp | 18 ++++++ TestJava.cpp | 59 ++++++++++++++++++ 6 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 FrameworkTypes.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e1263d4..0f1e17f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ set(SOURCES JavaHandle.cpp TestJava.cpp Containers.cpp + FrameworkTypes.cpp ) POTHOS_MODULE_UTIL( diff --git a/FrameworkTypes.cpp b/FrameworkTypes.cpp new file mode 100644 index 0000000..aa5d2b5 --- /dev/null +++ b/FrameworkTypes.cpp @@ -0,0 +1,150 @@ +// Copyright (c) 2019 Nicholas Corgan +// SPDX-License-Identifier: BSL-1.0 + +#include "JavaProxy.hpp" + +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +static Pothos::Proxy convertBufferChunkToJavaByteBuffer( + Pothos::ProxyEnvironment::Sptr env, + const Pothos::BufferChunk& buffer) +{ + auto javaProxyEnvironment = std::dynamic_pointer_cast(env); + jobject jniByteBuffer = javaProxyEnvironment->env->NewDirectByteBuffer( + reinterpret_cast(buffer.address), + static_cast(buffer.length)); + + auto bufferProxy = javaProxyEnvironment->makeHandle(jniByteBuffer); + + // Cast the return buffer type to the correct subclass based on the + // BufferChunk's dtype. + static const std::unordered_map dtypeToJavaFunc = + { + {"int8", "asCharBuffer"}, + {"int16", "asShortBuffer"}, + {"int32", "asIntBuffer"}, + {"int64", "asLongBuffer"}, + {"float32", "asFloatBuffer"}, + {"float64", "asDoubleBuffer"} + }; + auto mapIter = dtypeToJavaFunc.find(buffer.dtype.name()); + if(mapIter != dtypeToJavaFunc.end()) + { + const std::string& castFunc = mapIter->second; + bufferProxy = bufferProxy.call(castFunc); + } + else + { + throw Pothos::InvalidArgumentException("Invalid or unsupported DType: "+buffer.dtype.name()); + } + + return bufferProxy; +} + +static bool isJavaProxySubclass( + const Pothos::Proxy& proxy, + const std::string& destClass) +{ + auto env = proxy.getEnvironment(); + + auto proxyJavaClass = proxy.call("class"); + auto destJavaClass = env->findProxy(destClass); + + return destJavaClass.call("isAssignableFrom", proxyJavaClass); +} + +static Pothos::BufferChunk convertJavaByteBufferToBufferChunk(const Pothos::Proxy& byteBuffer) +{ + // The given byte buffer must be direct (meaning it has a "backing" array + // that can be accessed by native code). + if(!byteBuffer.call("isDirect")) + { + throw Pothos::InvalidArgumentException("The given "+byteBuffer.getClassName()+" cannot be accessed by native code."); + } + + auto javaProxyEnvironment = std::dynamic_pointer_cast( + byteBuffer.getEnvironment()); + jobject jniByteBuffer = javaProxyEnvironment->getHandle(byteBuffer)->toJobject(); + + void* address = javaProxyEnvironment->env->GetDirectBufferAddress(jniByteBuffer); + jlong capacity = javaProxyEnvironment->env->GetDirectBufferCapacity(jniByteBuffer); + + static const std::unordered_map javaClassToDType = + { + {"java.nio.ByteBuffer", "int8"}, + {"java.nio.CharBuffer", "int8"}, + {"java.nio.ShortBuffer", "int16"}, + {"java.nio.IntBuffer", "int32"}, + {"java.nio.LongBuffer", "int64"}, + {"java.nio.FloatBuffer", "float32"}, + {"java.nio.DoubleBuffer", "float64"}, + }; + using MapPair = std::unordered_map::value_type; + + auto applicableClassIter = std::find_if( + javaClassToDType.begin(), + javaClassToDType.end(), + [&byteBuffer](const MapPair& mapPair) + { + return isJavaProxySubclass(byteBuffer, mapPair.first); + }); + if(applicableClassIter == javaClassToDType.end()) + { + throw Pothos::InvalidArgumentException("Could not find valid DType for "+byteBuffer.getClassName()); + } + + Pothos::DType dtype(applicableClassIter->second); + + auto sharedBuff = Pothos::SharedBuffer( + reinterpret_cast(address), + static_cast(capacity * dtype.elemSize()), + byteBuffer.getHandle()); + auto chunk = Pothos::BufferChunk(sharedBuff); + chunk.dtype = dtype; + + return chunk; +} + +pothos_static_block(pothosRegisterJavaByteBufferConversions) +{ + Pothos::PluginRegistry::addCall( + "/proxy/converters/java/bufferchunk_to_java_bytebuffer", + &convertBufferChunkToJavaByteBuffer); + + const std::vector compatibleByteBufferClasses = + { + "ByteBuffer", + "CharBuffer", + "ShortBuffer", + "IntBuffer", + "LongBuffer", + "FloatBuffer", + "DoubleBuffer" + }; + for(const std::string& byteBufferClass: compatibleByteBufferClasses) + { + const std::string lowerName = Poco::toLower(byteBufferClass); + + Pothos::PluginRegistry::add( + "/proxy/converters/java/java_"+lowerName+"_to_bufferchunk", + Pothos::ProxyConvertPair( + "java.nio."+byteBufferClass, + &convertJavaByteBufferToBufferChunk)); + } +} diff --git a/JavaHandle.cpp b/JavaHandle.cpp index 153a7a3..4358aaa 100644 --- a/JavaHandle.cpp +++ b/JavaHandle.cpp @@ -5,9 +5,10 @@ #include #include #include +#include JavaProxyHandle::JavaProxyHandle(std::shared_ptr env, jvalue value, char sig): - env(env), value(value), sig(sig) + env(env), value(value), sig(sig), inheritance(getInheritance()), getClassNameDepth(0) { assert(isupper(sig)); } @@ -61,13 +62,51 @@ std::string JavaProxyHandle::toString(void) const return env->jstringToString(env->env->CallObjectMethod(this->toJobject(), toString)); } +Pothos::Proxy JavaProxyHandle::getProxyWithSuperclassName(void) const +{ + if(!hasSuperclass()) + { + throw Pothos::BadCastException( + "JavaProxyHandle::getHandleWithSuperclassName()", + "No subclass for "+this->getClassName()); + } + + auto superclassHandle = std::make_shared(*this); + ++superclassHandle->getClassNameDepth; + + return Pothos::Proxy(superclassHandle); +} + std::string JavaProxyHandle::getClassName(void) const { - if (sig != 'L') return std::string(1, this->sig); - if (value.l == nullptr) return ""; - jclass cls = env->env->GetObjectClass(value.l); - assert(cls != nullptr); - return env->getClassName(cls); + if((sig == 'L') && (value.l != nullptr)) + { + assert(getClassNameDepth < inheritance.size()); + return inheritance[getClassNameDepth]; + } + else if (sig != 'L') return std::string(1, this->sig); + + return ""; +} + +std::vector JavaProxyHandle::getInheritance(void) const +{ + std::vector superclassNames; + if((sig == 'L') && (value.l != nullptr)) + { + jclass cls = env->env->GetObjectClass(value.l); + while(cls != nullptr) + { + superclassNames.emplace_back(env->getClassName(cls)); + cls = env->env->GetSuperclass(cls); + } + } + else + { + superclassNames.emplace_back(this->getClassName()); + } + + return superclassNames; } /*********************************************************************** diff --git a/JavaProxy.cpp b/JavaProxy.cpp index d58ed38..593f1d2 100644 --- a/JavaProxy.cpp +++ b/JavaProxy.cpp @@ -1,10 +1,12 @@ // Copyright (c) 2013-2014 Josh Blum +// 2019 Nicholas Corgan // SPDX-License-Identifier: BSL-1.0 #include "JavaProxy.hpp" #include #include #include +#include #include #include #include @@ -149,6 +151,37 @@ Pothos::Proxy JavaProxyEnvironment::deserialize(std::istream &is) } } +Pothos::Object JavaProxyEnvironment::convertProxyToObject(const Pothos::Proxy &proxy_) +{ + // First, try the parent method. + try + { + return Pothos::ProxyEnvironment::convertProxyToObject(proxy_); + } + catch (const Pothos::ProxyEnvironmentConvertError&) {} + + Pothos::Proxy proxy = proxy_; + auto javaHandle = std::dynamic_pointer_cast(proxy.getHandle()); + + while(javaHandle->hasSuperclass()) + { + proxy = javaHandle->getProxyWithSuperclassName(); + javaHandle = std::dynamic_pointer_cast(proxy.getHandle()); + try + { + return Pothos::ProxyEnvironment::convertProxyToObject(proxy); + } + catch(const Pothos::ProxyEnvironmentConvertError&) {} + } + + // At this point, we've tried everything up to java.lang.Object, so if + // there's no conversion so far, there's no conversion at all. + throw Pothos::ProxyEnvironmentConvertError( + "JavaProxyEnvironment::convertProxyToObject()", + Poco::format("cannot convert %s or any superclasses to Pothos::Object", + proxy_.getClassName())); +} + /*********************************************************************** * factory registration **********************************************************************/ diff --git a/JavaProxy.hpp b/JavaProxy.hpp index c147550..1970b79 100644 --- a/JavaProxy.hpp +++ b/JavaProxy.hpp @@ -1,4 +1,5 @@ // Copyright (c) 2013-2014 Josh Blum +// 2019 Nicholas Corgan // SPDX-License-Identifier: BSL-1.0 #pragma once @@ -6,6 +7,7 @@ #include #include #include +#include class JavaProxyHandle; @@ -49,6 +51,7 @@ class JavaProxyEnvironment : void serialize(const Pothos::Proxy &, std::ostream &); Pothos::Proxy deserialize(std::istream &); + Pothos::Object convertProxyToObject(const Pothos::Proxy &proxy_); JavaVM *jvm; /* pointer to open virtual machine */ JNIEnv *env; /* pointer to native method interface */ @@ -74,6 +77,7 @@ class JavaProxyHandle : public Pothos::ProxyHandle size_t hashCode(void) const; std::string toString(void) const; std::string getClassName(void) const; + std::vector getInheritance(void) const; std::shared_ptr env; @@ -90,4 +94,18 @@ class JavaProxyHandle : public Pothos::ProxyHandle //convert internal jvalue which may be a primitive to equivalent jobject jobject toJobject(void) const; + + // A list of classes that make up this object's inheritance. + std::vector inheritance; + + // How many superclasses to climb up to get the return value for + // getClassName(). + size_t getClassNameDepth; + + inline bool hasSuperclass(void) const + { + return (getClassNameDepth < (inheritance.size()-1)); + } + + Pothos::Proxy getProxyWithSuperclassName(void) const; }; diff --git a/TestJava.cpp b/TestJava.cpp index 7125fd3..47ea1c6 100644 --- a/TestJava.cpp +++ b/TestJava.cpp @@ -1,7 +1,11 @@ // Copyright (c) 2013-2015 Josh Blum +// 2019 Nicholas Corgan // SPDX-License-Identifier: BSL-1.0 +#include "JavaProxy.hpp" + #include +#include #include #include #include @@ -237,3 +241,58 @@ POTHOS_TEST_BLOCK("/proxy/java/tests", test_serialization) POTHOS_TEST_TRUE(find1 != resultDict.end()); POTHOS_TEST_EQUAL(find1->second.convert(), 2); } + +POTHOS_TEST_BLOCK("/proxy/java/tests", test_get_inheritance) +{ + auto env = Pothos::ProxyEnvironment::make("java"); + + const std::vector expectedInheritance = + { + "java.io.PipedInputStream", + "java.io.InputStream", + "java.lang.Object" + }; + const std::string& startClassName = expectedInheritance.front(); + + auto startClassInstance = env->findProxy(startClassName).call("new"); + auto javaHandle = std::dynamic_pointer_cast(startClassInstance.getHandle()); + auto inheritance = javaHandle->getInheritance(); + + POTHOS_TEST_EQUALV(expectedInheritance, inheritance); +} + +template +static void testBufferChunkConversion(Pothos::ProxyEnvironment::Sptr env) +{ + const Pothos::DType dtype(typeid(T)); + std::cout << "Testing " << dtype.toString() << std::endl; + + Pothos::BufferChunk buffIn(dtype, 128); + for(size_t i = 0; i < buffIn.elements(); ++i) + { + buffIn.as()[i] = T(std::rand() % 100); + } + + // Convert into a Java direct buffer. + auto javaBuff = env->makeProxy(buffIn); + + // Convert back and check for equality. + const auto buffOut = javaBuff.convert(); + POTHOS_TEST_EQUAL(buffIn.dtype, buffOut.dtype); + POTHOS_TEST_EQUALA( + buffIn.as(), + buffOut.as(), + buffOut.elements()); +} + +POTHOS_TEST_BLOCK("/proxy/java/tests", test_bufferchunk_conversion) +{ + auto env = Pothos::ProxyEnvironment::make("java"); + + testBufferChunkConversion(env); + testBufferChunkConversion(env); + testBufferChunkConversion(env); + testBufferChunkConversion(env); + testBufferChunkConversion(env); + testBufferChunkConversion(env); +}