// Copyright 2019 taylorcyang@tencent.com
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// This file is part of the Jenny project.
// https://github.com/LanderlYoung/Jenny
/**
*
* Author: landerlyoung@gmail.com
* Date: 2019-07-24
* Time: 20:29
* Life with Passion, Code with Creativity.
*
*/
#pragma once
#include
#include // assert
#include // memcpy, memset
#include // swap, move
#include
#if defined(__has_include) && __has_include()
#include
#endif
#ifdef __ANDROID__
#include
#endif
namespace jenny {
/**
* persisted jni env scope.
* attach current thread to jvm, until it's dead.
*/
class Env {
private:
JNIEnv* _env;
public:
/**
* MUST be called before any calls.
* suggested on JNI_OnLoad
*/
static void attachJvm(JavaVM* jvm);
static void attachJvm(JNIEnv* env) {
JavaVM* jvm;
env->GetJavaVM(&jvm);
attachJvm(jvm);
}
public:
Env() : _env(attachCurrentThreadIfNeed()) {}
JNIEnv* operator->() const { return _env; }
JNIEnv* get() const { return _env; }
private:
struct StaticState;
static StaticState* staticState();
static JNIEnv* attachCurrentThreadIfNeed();
};
template
class GlobalRef;
template
class LocalRef {
private:
JniPointer _value;
JNIEnv* _env;
bool _owned;
public:
LocalRef() : LocalRef(nullptr, nullptr, false) {}
explicit LocalRef(JniPointer value, bool owned = true) : LocalRef(Env().get(), value, owned) {}
LocalRef(JNIEnv* env, JniPointer value = nullptr, bool owned = true)
: _value(value), _env(env), _owned(owned) {
assert(value == nullptr || env->GetObjectRefType(value) == jobjectRefType::JNILocalRefType);
}
LocalRef(const LocalRef& copy) : _value(nullptr), _env(nullptr), _owned(true) {
if (copy._value) {
_env = copy._env;
_value = reinterpret_cast(_env->NewLocalRef(copy._value));
_owned = true;
assert(_value);
}
}
LocalRef(LocalRef&& from) noexcept : _value(from._value), _env(from._env), _owned(from._owned) {
from._value = nullptr;
from._env = nullptr;
from._owned = false;
}
LocalRef& operator=(LocalRef other) noexcept {
swap(other);
return *this;
}
void swap(LocalRef& other) noexcept {
std::swap(_value, other._value);
std::swap(_env, other._env);
std::swap(_owned, other._owned);
}
~LocalRef() {
if (_value && _owned) {
_env->DeleteLocalRef(_value);
_env = nullptr;
}
}
GlobalRef toGlobal() const;
JniPointer get() const { return _value; }
bool owned() const { return _owned; }
/**
* same as unique_ptr::release, return the ref out, and give of the ownership.
*/
JniPointer release() {
auto ret = _value;
_value = nullptr;
return ret;
}
operator bool() const { return _value != nullptr; }
};
template
class GlobalRef {
private:
JniPointer _value = nullptr;
public:
explicit GlobalRef(JniPointer value = nullptr) : GlobalRef(Env().get(), value) {}
explicit GlobalRef(const LocalRef& local) : GlobalRef(local.get()) {}
explicit GlobalRef(JNIEnv* env, JniPointer value = nullptr) {
if (value) {
_value = reinterpret_cast(env->NewGlobalRef(value));
}
}
GlobalRef(const GlobalRef& from) noexcept : GlobalRef(from._value) {}
GlobalRef(GlobalRef&& from) noexcept : _value(from._value) { from._value = nullptr; }
GlobalRef& operator=(GlobalRef from) noexcept {
swap(from);
return *this;
}
void swap(GlobalRef& other) noexcept { std::swap(_value, other._value); }
LocalRef toLocal() const {
if (_value) {
return LocalRef(reinterpret_cast(Env()->NewLocalRef(_value)));
} else {
return {};
}
}
~GlobalRef() { clear(); }
void clear() {
if (_value) {
Env()->DeleteGlobalRef(_value);
_value = nullptr;
}
}
JniPointer get() const { return _value; }
/**
* same as unique_ptr::release, return the global ref out, and give of the ownership.
*/
JniPointer release() {
auto ret = _value;
_value = nullptr;
return ret;
}
operator bool() const { return _value != nullptr; }
};
inline bool checkUtfBytes(const char* bytes) {
if (bytes == nullptr) return false;
while (*bytes != '\0') {
const uint8_t* utf8 = reinterpret_cast(bytes++);
// Switch on the high four bits.
switch (*utf8 >> 4) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
// Bit pattern 0xxx. No need for any extra bytes.
break;
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
// Bit patterns 10xx, which are illegal start bytes.
return false;
case 0x0f:
// Bit pattern 1111, which might be the start of a 4 byte sequence.
if ((*utf8 & 0x08) == 0) {
// Bit pattern 1111 0xxx, which is the start of a 4 byte sequence.
// We consume one continuation byte here, and fall through to consume two more.
utf8 = reinterpret_cast(bytes++);
if ((*utf8 & 0xc0) != 0x80) {
return false;
}
} else {
return false;
}
// Fall through to the cases below to consume two more continuation bytes.
case 0x0e:
// Bit pattern 1110, so there are two additional bytes.
utf8 = reinterpret_cast(bytes++);
if ((*utf8 & 0xc0) != 0x80) {
return false;
}
// Fall through to consume one more continuation byte.
case 0x0c:
case 0x0d:
// Bit pattern 110x, so there is one additional byte.
utf8 = reinterpret_cast(bytes++);
if ((*utf8 & 0xc0) != 0x80) {
return false;
}
break;
}
}
return true;
}
template
GlobalRef LocalRef::toGlobal() const {
return GlobalRef(_env, _value);
}
inline LocalRef toJavaString(JNIEnv* env, const char* rawString) {
if (!checkUtfBytes(rawString)) {
// return null on failure
return {};
}
return LocalRef(env->NewStringUTF(rawString));
}
inline LocalRef toJavaString(const char* rawString) {
return toJavaString(Env().get(), rawString);
}
/**
* this function will do string memcpy. For local variable prefer JStringHolder instead.
* @param env
* @param rawString
* @return
*/
inline std::string fromJavaString(JNIEnv* env, jstring string) {
if (string == nullptr) {
return {};
}
auto cstr = env->GetStringUTFChars(string, nullptr);
std::string ret(cstr);
env->ReleaseStringUTFChars(string, cstr);
return ret;
}
inline std::string fromJavaString(const LocalRef& string) {
return fromJavaString(Env().get(), string.get());
}
class StringHolder {
private:
JNIEnv* _env;
jstring _jstr;
const char* _cStr;
jsize _length;
public:
StringHolder(JNIEnv* env, jstring jstr)
: _env(env),
_jstr(jstr),
_cStr(jstr ? env->GetStringUTFChars(jstr, nullptr) : nullptr),
_length(jstr ? env->GetStringUTFLength(jstr) : 0) {}
StringHolder(const LocalRef& jstr) : StringHolder(Env().get(), jstr.get()) {}
StringHolder(const StringHolder&) = delete;
StringHolder& operator=(const StringHolder&) = delete;
StringHolder(StringHolder&& move) noexcept
: _env(move._env), _jstr(move._jstr), _cStr(move._cStr), _length(move._length) {
move._cStr = nullptr;
move._jstr = nullptr;
move._env = nullptr;
move._length = 0;
}
StringHolder& operator=(StringHolder&& from) noexcept {
if (_cStr) {
_env->ReleaseStringUTFChars(_jstr, _cStr);
}
_env = from._env;
_cStr = from._cStr;
_jstr = from._jstr;
_length = from._length;
from._env = nullptr;
from._cStr = nullptr;
from._jstr = nullptr;
from._length = 0;
return *this;
}
~StringHolder() {
if (_cStr) {
_env->ReleaseStringUTFChars(_jstr, _cStr);
}
}
/**
* @return return "" instead of nullptr
*/
const char* c_str() const {
if (_cStr == nullptr) return "";
return _cStr;
}
const size_t length() const { return static_cast(_length); }
#ifdef __cpp_lib_string_view
const std::string_view view() const { return std::string_view(c_str(), length()); }
#endif
};
class ByteArrayHolder {
jenny::Env env_;
jbyteArray array_;
jbyte* data_ = nullptr;
size_t length_ = 0;
bool commit_ = false;
public:
ByteArrayHolder(const jenny::LocalRef& array) : ByteArrayHolder(array.get()) {}
ByteArrayHolder(jbyteArray array) : env_(), array_(array) {
if (array) {
jboolean isCopy;
data_ = env_->GetByteArrayElements(array, &isCopy);
length_ = env_->GetArrayLength(array);
}
}
ByteArrayHolder(const ByteArrayHolder&) = delete;
ByteArrayHolder& operator=(const ByteArrayHolder&) = delete;
jbyte* data() const { return data_; }
size_t length() const { return length_; }
void commit(bool doCommit = true) { commit_ = doCommit; }
~ByteArrayHolder() {
if (array_) {
env_->ReleaseByteArrayElements(array_, data(), commit_ ? JNI_COMMIT : JNI_ABORT);
}
}
};
inline jenny::LocalRef makeByteArray(size_t len, const void* data = nullptr) {
jenny::Env env;
jenny::LocalRef array(env->NewByteArray(len));
if (data) {
jboolean isCopy;
auto ptr = env->GetByteArrayElements(array.get(), &isCopy);
std::memcpy(ptr, data, len);
env->ReleaseByteArrayElements(array.get(), ptr, JNI_COMMIT);
}
return array;
}
inline void copyFromByteArray(const LocalRef& array, void* dst, size_t len) {
jenny::Env env;
jboolean copy;
auto buf = env->GetByteArrayElements(array.get(), ©);
if (len == 0) {
len = env->GetArrayLength(array.get());
}
std::memcpy(dst, buf, len);
env->ReleaseByteArrayElements(array.get(), buf, JNI_ABORT);
}
inline void copyToByteArray(const LocalRef& array, const void* src, size_t len) {
jenny::Env env;
jboolean copy;
auto buf = env->GetByteArrayElements(array.get(), ©);
if (len == 0) {
len = env->GetArrayLength(array.get());
}
std::memcpy(buf, src, len);
env->ReleaseByteArrayElements(array.get(), buf, JNI_COMMIT);
}
/**
* \code
*
* TryCatch tryCatch0;
* {
* TryCatch tryCatch;
* jenv->Throw(static_cast(runtimeException));
* assert(tryCatch.hasCaught());
* assert(tryCatch.getAndClearException());
* }
* assert(!tryCatch0.hasCaught());
*
* {
* TryCatch tryCatch;
* jenv->Throw(static_cast(runtimeException));
* auto ex = tryCatch.getAndClearException();
* tryCatch.throwException(ex);
* }
* assert(tryCatch0.hasCaught());
* tryCatch0.clearException();
*
* {
* TryCatch tryCatch;
* jenv->Throw(static_cast(runtimeException.getThis(false).get()));
* tryCatch.rethrowException();
* }
* assert(tryCatch0.hasCaught());
*
* \endcode
*/
class TryCatch {
private:
JNIEnv* _env;
bool _rethrow;
public:
explicit TryCatch(JNIEnv* env) : _env(env), _rethrow(false) { assert(!env->ExceptionCheck()); }
explicit TryCatch() : TryCatch(Env().get()) {}
TryCatch(const TryCatch&) = delete;
TryCatch& operator=(const TryCatch&) = delete;
bool hasCaught() const { return _env->ExceptionCheck(); }
/**
* get current exception if any, and clear it.
*
* note: In order to do any JNI operations after words, the "current exception" must be cleared
* first, otherwise JVM would abort.
*/
LocalRef getAndClearException() {
jthrowable e = _env->ExceptionOccurred();
if (e) {
_env->ExceptionClear();
}
return LocalRef(_env, e);
}
/**
* clear current exception.
* @return true for has pending and cleared. false for no pending exception.
*/
bool clearException() {
if (hasCaught()) {
_env->ExceptionClear();
return true;
}
return false;
}
/**
* rethrow an exception, and this TryCatch won't do clear exception in dtor again.
*/
void throwException(const LocalRef& e) {
if (e) {
_rethrow = true;
_env->Throw(e.get());
}
}
/**
* If hasCaught, rethrow the current exception (won't do clear exception in dtor again.)
* @return has pending exception or not
*/
bool rethrowException() {
if (hasCaught()) {
_rethrow = true;
return true;
}
return false;
}
~TryCatch() {
if (!_rethrow && hasCaught()) {
_env->ExceptionClear();
}
}
};
// impl for Env
#ifdef __ANDROID__
struct Env::StaticState {
JavaVM* _jvm{};
::pthread_key_t envWrapperKey{};
StaticState() {
::pthread_key_create(&envWrapperKey, [](void*) {
auto state = Env::staticState();
state->_jvm->DetachCurrentThread();
});
}
};
inline Env::StaticState* Env::staticState() {
static Env::StaticState state{};
return &state;
}
inline void Env::attachJvm(JavaVM* jvm) {
auto state = staticState();
state->_jvm = jvm;
}
inline JNIEnv* Env::attachCurrentThreadIfNeed() {
JNIEnv* env;
auto state = staticState();
assert(state->_jvm &&
"please call ::jenny::Env::attachJvm before any usage. (JNI_OnLoad is recommended.)");
auto jvm = state->_jvm;
if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == JNI_EDETACHED) {
assert(jvm);
// thread_local on some version of NDK have bug, dtor not called...
env = static_cast(::pthread_getspecific(state->envWrapperKey));
if (!env) {
jvm->AttachCurrentThread(&env, nullptr);
assert(env);
::pthread_setspecific(state->envWrapperKey, env);
}
}
assert(env != nullptr);
return env;
}
#else
struct Env::StaticState {
JavaVM* _jvm{};
};
inline Env::StaticState* Env::staticState() {
static Env::StaticState state{};
return &state;
}
inline void Env::attachJvm(JavaVM* jvm) {
assert(jvm);
auto state = staticState();
state->_jvm = jvm;
}
inline JNIEnv* Env::attachCurrentThreadIfNeed() {
JNIEnv* env;
auto state = staticState();
assert(state->_jvm &&
"please call ::jenny::Env::attachJvm before any usage. (JNI_OnLoad is recommended.)");
auto jvm = state->_jvm;
if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == JNI_EDETACHED) {
struct EnvWrapper {
JavaVM* const jvm;
JNIEnv* env;
explicit EnvWrapper(JavaVM* _jvm) : jvm(_jvm), env(nullptr) {
_jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr);
assert(env != nullptr);
}
~EnvWrapper() {
// detach when our thread is exiting.
jvm->DetachCurrentThread();
}
};
thread_local EnvWrapper envWrapper(jvm);
env = envWrapper.env;
}
assert(env != nullptr);
return env;
}
#endif
#ifdef JENNY_JNI_HELPER_TEST
namespace internal {
// UnitTest
inline void jniHelperUnitTest(JNIEnv* env_) {
Env::attachJvm(env_);
Env env;
assert(env.get() == env_);
{
LocalRef str = toJavaString("hello");
assert(str);
LocalRef str_copy = str;
assert(str_copy);
LocalRef str_notOwn(str.get(), false);
assert(str_notOwn);
assert(env->IsSameObject(str.get(), str_copy.get()));
assert(env->IsSameObject(str.get(), str_notOwn.get()));
LocalRef move = std::move(str);
assert(move);
assert(!str);
move = toJavaString("world");
assert(!env->IsSameObject(move.get(), str_copy.get()));
assert(move);
}
{
LocalRef str = toJavaString("hello");
GlobalRef glb(str);
assert(env->IsSameObject(glb.get(), str.get()));
GlobalRef glb_copy = glb;
assert(glb_copy);
assert(env->IsSameObject(glb.get(), glb_copy.get()));
GlobalRef glb_move = std::move(glb);
assert(glb_move);
assert(!glb);
glb_move = GlobalRef(toJavaString("world"));
assert(!env->IsSameObject(glb_move.get(), glb_copy.get()));
assert(glb_move);
}
{
std::string h = "hello";
LocalRef str = toJavaString(h.c_str());
assert(h == fromJavaString(str));
StringHolder sh(str);
#ifdef __cpp_lib_string_view
assert(sh.view() == h);
#endif
}
{
auto bytes = makeByteArray(1024);
uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7};
auto len = sizeof(data) / sizeof(data[0]);
copyToByteArray(bytes, data, len);
{
ByteArrayHolder holder(bytes);
assert(holder.length() == 1024);
assert(holder.data()[0] == 0);
assert(holder.data()[4] == 4);
assert(holder.data()[7] == 7);
assert(holder.data()[8] == 0);
}
{
std::memset(data, 0, len);
copyFromByteArray(bytes, data, len-1);
assert(data[0] == 0);
assert(data[4] == 4);
assert(data[7] == 0);
}
}
{
LocalRef runtimeExceptionClass(env->FindClass("java/lang/RuntimeException"));
TryCatch tryCatch0;
{
TryCatch tryCatch;
env->ThrowNew(runtimeExceptionClass.get(), nullptr);
assert(tryCatch.hasCaught());
assert(tryCatch.getAndClearException());
}
assert(!tryCatch0.hasCaught());
{
TryCatch tryCatch;
env->ThrowNew(runtimeExceptionClass.get(), nullptr);
auto ex = tryCatch.getAndClearException();
tryCatch.throwException(ex);
}
assert(tryCatch0.hasCaught());
tryCatch0.clearException();
{
TryCatch tryCatch;
env->ThrowNew(runtimeExceptionClass.get(), nullptr);
tryCatch.rethrowException();
}
assert(tryCatch0.hasCaught());
}
}
} // namespace internal
#endif //JENNY_JNI_HELPER_TEST
} // namespace jenny