blob: caeb85917c150e9adcfba3bac57132373e4b3b41 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "include/core/SkTypes.h"
#include "platform_tools/android/apps/skottie/src/main/cpp/JavaInputStreamAdaptor.h"
static jclass findClassCheck(JNIEnv* env, const char classname[]) {
jclass clazz = env->FindClass(classname);
SkASSERT(!env->ExceptionCheck());
return clazz;
}
static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
const char methodname[], const char type[]) {
jmethodID id = env->GetMethodID(clazz, methodname, type);
SkASSERT(!env->ExceptionCheck());
return id;
}
static jmethodID gInputStream_readMethodID;
static jmethodID gInputStream_skipMethodID;
static JNIEnv* get_env_or_die(JavaVM* jvm) {
JNIEnv* env;
if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
char errorMessage[256];
sprintf(errorMessage, "Failed to get JNIEnv for JavaVM: %p", jvm);
SK_ABORT(errorMessage);
}
return env;
}
/**
* Wrapper for a Java InputStream.
*/
class JavaInputStreamAdaptor : public SkStream {
JavaInputStreamAdaptor(JavaVM* jvm, jobject js, jbyteArray ar, jint capacity,
bool swallowExceptions)
: fJvm(jvm)
, fJavaInputStream(js)
, fJavaByteArray(ar)
, fCapacity(capacity)
, fBytesRead(0)
, fIsAtEnd(false)
, fSwallowExceptions(swallowExceptions) {}
public:
static JavaInputStreamAdaptor* Create(JNIEnv* env, jobject js, jbyteArray ar,
bool swallowExceptions) {
JavaVM* jvm;
if (env->GetJavaVM(&jvm) != JNI_OK) {
SK_ABORT("Failed to get JavaVM");
}
js = env->NewGlobalRef(js);
if (!js) {
return nullptr;
}
ar = (jbyteArray) env->NewGlobalRef(ar);
if (!ar) {
env->DeleteGlobalRef(js);
return nullptr;
}
jint capacity = env->GetArrayLength(ar);
return new JavaInputStreamAdaptor(jvm, js, ar, capacity, swallowExceptions);
}
~JavaInputStreamAdaptor() override {
auto* env = get_env_or_die(fJvm);
env->DeleteGlobalRef(fJavaInputStream);
env->DeleteGlobalRef(fJavaByteArray);
}
size_t read(void* buffer, size_t size) override {
auto* env = get_env_or_die(fJvm);
if (!fSwallowExceptions && checkException(env)) {
// Just in case the caller did not clear from a previous exception.
return 0;
}
if (NULL == buffer) {
if (0 == size) {
return 0;
} else {
/* InputStream.skip(n) can return <=0 but still not be at EOF
If we see that value, we need to call read(), which will
block if waiting for more data, or return -1 at EOF
*/
size_t amountSkipped = 0;
do {
size_t amount = this->doSkip(size - amountSkipped, env);
if (0 == amount) {
char tmp;
amount = this->doRead(&tmp, 1, env);
if (0 == amount) {
// if read returned 0, we're at EOF
fIsAtEnd = true;
break;
}
}
amountSkipped += amount;
} while (amountSkipped < size);
return amountSkipped;
}
}
return this->doRead(buffer, size, env);
}
bool isAtEnd() const override { return fIsAtEnd; }
private:
size_t doRead(void* buffer, size_t size, JNIEnv* env) {
size_t bytesRead = 0;
// read the bytes
do {
jint requested = 0;
if (size > static_cast<size_t>(fCapacity)) {
requested = fCapacity;
} else {
// This is safe because requested is clamped to (jint)
// fCapacity.
requested = static_cast<jint>(size);
}
jint n = env->CallIntMethod(fJavaInputStream,
gInputStream_readMethodID, fJavaByteArray, 0, requested);
if (checkException(env)) {
SkDebugf("---- read threw an exception\n");
return bytesRead;
}
if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
fIsAtEnd = true;
break; // eof
}
env->GetByteArrayRegion(fJavaByteArray, 0, n,
reinterpret_cast<jbyte*>(buffer));
if (checkException(env)) {
SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
return bytesRead;
}
buffer = (void*)((char*)buffer + n);
bytesRead += n;
size -= n;
fBytesRead += n;
} while (size != 0);
return bytesRead;
}
size_t doSkip(size_t size, JNIEnv* env) {
jlong skipped = env->CallLongMethod(fJavaInputStream,
gInputStream_skipMethodID, (jlong)size);
if (checkException(env)) {
SkDebugf("------- skip threw an exception\n");
return 0;
}
if (skipped < 0) {
skipped = 0;
}
return (size_t)skipped;
}
bool checkException(JNIEnv* env) {
if (!env->ExceptionCheck()) {
return false;
}
env->ExceptionDescribe();
if (fSwallowExceptions) {
env->ExceptionClear();
}
// There is no way to recover from the error, so consider the stream
// to be at the end.
fIsAtEnd = true;
return true;
}
JavaVM* fJvm;
jobject fJavaInputStream;
jbyteArray fJavaByteArray;
const jint fCapacity;
size_t fBytesRead;
bool fIsAtEnd;
const bool fSwallowExceptions;
};
static SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage,
bool swallowExceptions = true) {
return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
}
static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) {
SkASSERT(stream != NULL);
size_t bufferSize = 4096;
size_t streamLen = 0;
size_t len;
char* data = (char*)sk_malloc_throw(bufferSize);
while ((len = stream->read(data + streamLen,
bufferSize - streamLen)) != 0) {
streamLen += len;
if (streamLen == bufferSize) {
bufferSize *= 2;
data = (char*)sk_realloc_throw(data, bufferSize);
}
}
data = (char*)sk_realloc_throw(data, streamLen);
SkMemoryStream* streamMem = new SkMemoryStream();
streamMem->setMemoryOwned(data, streamLen);
return streamMem;
}
SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
jbyteArray storage) {
std::unique_ptr<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
if (NULL == adaptor.get()) {
return NULL;
}
return adaptor_to_mem_stream(adaptor.get());
}
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
return JNI_VERSION_1_6;
}