Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ set(SOURCES
JavaHandle.cpp
TestJava.cpp
Containers.cpp
FrameworkTypes.cpp
)

POTHOS_MODULE_UTIL(
Expand Down
150 changes: 150 additions & 0 deletions FrameworkTypes.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) 2019 Nicholas Corgan
// SPDX-License-Identifier: BSL-1.0

#include "JavaProxy.hpp"

#include <Pothos/Exception.hpp>
#include <Pothos/Plugin.hpp>
#include <Pothos/Proxy.hpp>

#include <Pothos/Framework/BufferChunk.hpp>
#include <Pothos/Framework/SharedBuffer.hpp>

#include <Poco/String.h>

#include <jni.h>

#include <algorithm>
#include <complex>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>

static Pothos::Proxy convertBufferChunkToJavaByteBuffer(
Pothos::ProxyEnvironment::Sptr env,
const Pothos::BufferChunk& buffer)
{
auto javaProxyEnvironment = std::dynamic_pointer_cast<JavaProxyEnvironment>(env);
jobject jniByteBuffer = javaProxyEnvironment->env->NewDirectByteBuffer(
reinterpret_cast<void*>(buffer.address),
static_cast<jlong>(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<std::string, std::string> 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<bool>("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<bool>("isDirect"))
{
throw Pothos::InvalidArgumentException("The given "+byteBuffer.getClassName()+" cannot be accessed by native code.");
}

auto javaProxyEnvironment = std::dynamic_pointer_cast<JavaProxyEnvironment>(
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<std::string, std::string> 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<std::string, std::string>::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<size_t>(address),
static_cast<size_t>(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<std::string> 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));
}
}
51 changes: 45 additions & 6 deletions JavaHandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
#include <iostream>
#include <cassert>
#include <cctype>
#include <memory>

JavaProxyHandle::JavaProxyHandle(std::shared_ptr<JavaProxyEnvironment> env, jvalue value, char sig):
env(env), value(value), sig(sig)
env(env), value(value), sig(sig), inheritance(getInheritance()), getClassNameDepth(0)
{
assert(isupper(sig));
}
Expand Down Expand Up @@ -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<JavaProxyHandle>(*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<std::string> JavaProxyHandle::getInheritance(void) const
{
std::vector<std::string> 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;
}

/***********************************************************************
Expand Down
33 changes: 33 additions & 0 deletions JavaProxy.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) 2013-2014 Josh Blum
// 2019 Nicholas Corgan
// SPDX-License-Identifier: BSL-1.0

#include "JavaProxy.hpp"
#include <Pothos/Callable.hpp>
#include <Pothos/Plugin.hpp>
#include <iostream>
#include <Poco/Format.h>
#include <Poco/SingletonHolder.h>
#include <cstdint>
#include <mutex>
Expand Down Expand Up @@ -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<JavaProxyHandle>(proxy.getHandle());

while(javaHandle->hasSuperclass())
{
proxy = javaHandle->getProxyWithSuperclassName();
javaHandle = std::dynamic_pointer_cast<JavaProxyHandle>(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
**********************************************************************/
Expand Down
18 changes: 18 additions & 0 deletions JavaProxy.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) 2013-2014 Josh Blum
// 2019 Nicholas Corgan
// SPDX-License-Identifier: BSL-1.0

#pragma once
#include <Pothos/Config.hpp>
#include <Pothos/Proxy.hpp>
#include <Pothos/Callable.hpp>
#include <jni.h>
#include <vector>

class JavaProxyHandle;

Expand Down Expand Up @@ -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 */
Expand All @@ -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<std::string> getInheritance(void) const;

std::shared_ptr<JavaProxyEnvironment> env;

Expand All @@ -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<std::string> 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;
};
59 changes: 59 additions & 0 deletions TestJava.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright (c) 2013-2015 Josh Blum
// 2019 Nicholas Corgan
// SPDX-License-Identifier: BSL-1.0

#include "JavaProxy.hpp"

#include <Pothos/Testing.hpp>
#include <Pothos/Framework.hpp>
#include <Pothos/Proxy.hpp>
#include <iostream>
#include <vector>
Expand Down Expand Up @@ -237,3 +241,58 @@ POTHOS_TEST_BLOCK("/proxy/java/tests", test_serialization)
POTHOS_TEST_TRUE(find1 != resultDict.end());
POTHOS_TEST_EQUAL(find1->second.convert<int>(), 2);
}

POTHOS_TEST_BLOCK("/proxy/java/tests", test_get_inheritance)
{
auto env = Pothos::ProxyEnvironment::make("java");

const std::vector<std::string> 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<JavaProxyHandle>(startClassInstance.getHandle());
auto inheritance = javaHandle->getInheritance();

POTHOS_TEST_EQUALV(expectedInheritance, inheritance);
}

template <typename T>
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<T*>()[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::BufferChunk>();
POTHOS_TEST_EQUAL(buffIn.dtype, buffOut.dtype);
POTHOS_TEST_EQUALA(
buffIn.as<const T*>(),
buffOut.as<const T*>(),
buffOut.elements());
}

POTHOS_TEST_BLOCK("/proxy/java/tests", test_bufferchunk_conversion)
{
auto env = Pothos::ProxyEnvironment::make("java");

testBufferChunkConversion<std::int8_t>(env);
testBufferChunkConversion<std::int16_t>(env);
testBufferChunkConversion<std::int32_t>(env);
testBufferChunkConversion<std::int64_t>(env);
testBufferChunkConversion<float>(env);
testBufferChunkConversion<double>(env);
}