mirror of
https://github.com/bab2min/Kiwi.git
synced 2026-06-17 01:54:27 +00:00
Refactor Java StreamProvider to use lazy streaming instead of loading all data into memory
- Replace synchronous InputStream reading with custom JavaStreamAdapter - Implement JavaStreamBuf that reads data on-demand via underflow() - Disable seeking operations as Java InputStream doesn't support random access - Proper JNI lifecycle management with thread attachment/detachment - Memory-efficient chunk-based reading (8KB buffer) instead of loading entire files - Similar pattern to CStreamAdapter in C API for consistency Co-authored-by: bab2min <19266222+bab2min@users.noreply.github.com>
This commit is contained in:
parent
89cb859beb
commit
513ce92739
1 changed files with 122 additions and 28 deletions
|
|
@ -547,6 +547,125 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// Custom istream implementation that reads from Java InputStream on-demand
|
||||
class JavaStreamAdapter : public std::istream {
|
||||
private:
|
||||
class JavaStreamBuf : public std::streambuf {
|
||||
private:
|
||||
JavaVM* jvm;
|
||||
jobject inputStreamGlobalRef;
|
||||
jmethodID readMethod;
|
||||
jmethodID closeMethod;
|
||||
std::vector<char> buffer;
|
||||
static constexpr const size_t buffer_size = 8192;
|
||||
bool closed;
|
||||
|
||||
public:
|
||||
JavaStreamBuf(JavaVM* vm, jobject inputStream)
|
||||
: jvm(vm), inputStreamGlobalRef(nullptr), readMethod(nullptr), closeMethod(nullptr),
|
||||
buffer(buffer_size), closed(false) {
|
||||
|
||||
JNIEnv* env = nullptr;
|
||||
if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_8) == JNI_OK) {
|
||||
inputStreamGlobalRef = env->NewGlobalRef(inputStream);
|
||||
|
||||
jclass inputStreamClass = env->FindClass("java/io/InputStream");
|
||||
readMethod = env->GetMethodID(inputStreamClass, "read", "([B)I");
|
||||
closeMethod = env->GetMethodID(inputStreamClass, "close", "()V");
|
||||
}
|
||||
|
||||
setg(buffer.data(), buffer.data(), buffer.data());
|
||||
}
|
||||
|
||||
~JavaStreamBuf() {
|
||||
if (!closed && inputStreamGlobalRef && closeMethod) {
|
||||
JNIEnv* env = nullptr;
|
||||
bool shouldDetach = false;
|
||||
|
||||
jint getEnvResult = jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_8);
|
||||
if (getEnvResult == JNI_EDETACHED) {
|
||||
if (jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), nullptr) == JNI_OK) {
|
||||
shouldDetach = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (env) {
|
||||
env->CallVoidMethod(inputStreamGlobalRef, closeMethod);
|
||||
env->DeleteGlobalRef(inputStreamGlobalRef);
|
||||
if (shouldDetach) jvm->DetachCurrentThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int underflow() override {
|
||||
if (gptr() < egptr()) {
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
if (closed || !inputStreamGlobalRef || !readMethod) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
JNIEnv* env = nullptr;
|
||||
bool shouldDetach = false;
|
||||
|
||||
jint getEnvResult = jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_8);
|
||||
if (getEnvResult == JNI_EDETACHED) {
|
||||
if (jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), nullptr) != JNI_OK) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
shouldDetach = true;
|
||||
}
|
||||
else if (getEnvResult != JNI_OK) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
try {
|
||||
jbyteArray byteArray = env->NewByteArray(buffer_size);
|
||||
jint bytesRead = env->CallIntMethod(inputStreamGlobalRef, readMethod, byteArray);
|
||||
|
||||
if (bytesRead <= 0 || env->ExceptionCheck()) {
|
||||
if (env->ExceptionCheck()) env->ExceptionClear();
|
||||
env->DeleteLocalRef(byteArray);
|
||||
if (shouldDetach) jvm->DetachCurrentThread();
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
jbyte* bytes = env->GetByteArrayElements(byteArray, nullptr);
|
||||
std::copy(reinterpret_cast<char*>(bytes),
|
||||
reinterpret_cast<char*>(bytes + bytesRead),
|
||||
buffer.data());
|
||||
env->ReleaseByteArrayElements(byteArray, bytes, JNI_ABORT);
|
||||
env->DeleteLocalRef(byteArray);
|
||||
|
||||
if (shouldDetach) jvm->DetachCurrentThread();
|
||||
|
||||
setg(buffer.data(), buffer.data(), buffer.data() + bytesRead);
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
catch (...) {
|
||||
if (shouldDetach) jvm->DetachCurrentThread();
|
||||
return traits_type::eof();
|
||||
}
|
||||
}
|
||||
|
||||
// Java InputStream doesn't support random access, so seeking fails
|
||||
pos_type seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode) override {
|
||||
return pos_type(-1);
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type, std::ios_base::openmode) override {
|
||||
return pos_type(-1);
|
||||
}
|
||||
};
|
||||
|
||||
JavaStreamBuf buf;
|
||||
|
||||
public:
|
||||
JavaStreamAdapter(JavaVM* jvm, jobject inputStream) : std::istream(&buf), buf(jvm, inputStream) {}
|
||||
};
|
||||
|
||||
kiwi::KiwiBuilder::StreamProvider createStreamProviderWrapper(jni::JRef<JStreamProvider> streamProvider)
|
||||
{
|
||||
JNIEnv* env = getCurrentEnv();
|
||||
|
|
@ -592,37 +711,12 @@ private:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Read the InputStream into a byte array
|
||||
jclass inputStreamClass = env->FindClass("java/io/InputStream");
|
||||
jmethodID availableMethod = env->GetMethodID(inputStreamClass, "available", "()I");
|
||||
jmethodID readMethod = env->GetMethodID(inputStreamClass, "read", "([B)I");
|
||||
jmethodID closeMethod = env->GetMethodID(inputStreamClass, "close", "()V");
|
||||
|
||||
jint available = env->CallIntMethod(inputStream, availableMethod);
|
||||
if (available <= 0) available = 1024 * 1024; // Default to 1MB if available() returns 0
|
||||
|
||||
jbyteArray byteArray = env->NewByteArray(available);
|
||||
std::vector<char> buffer;
|
||||
|
||||
int totalRead = 0;
|
||||
while (true)
|
||||
{
|
||||
jint bytesRead = env->CallIntMethod(inputStream, readMethod, byteArray);
|
||||
if (bytesRead <= 0) break;
|
||||
|
||||
jbyte* bytes = env->GetByteArrayElements(byteArray, nullptr);
|
||||
buffer.insert(buffer.end(), reinterpret_cast<char*>(bytes), reinterpret_cast<char*>(bytes + bytesRead));
|
||||
env->ReleaseByteArrayElements(byteArray, bytes, JNI_ABORT);
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(inputStream, closeMethod);
|
||||
// Create streaming adapter that reads on-demand
|
||||
auto adapter = std::make_unique<JavaStreamAdapter>(jvm, inputStream);
|
||||
|
||||
if (shouldDetach) jvm->DetachCurrentThread();
|
||||
|
||||
// Create string stream from buffer
|
||||
std::string data(buffer.begin(), buffer.end());
|
||||
return std::make_unique<std::istringstream>(std::move(data));
|
||||
return std::move(adapter);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue