18034f7806
GitOrigin-RevId: 5cc7cc927d0195bcdc7f8adbeea052266dff6af5
353 lines
11 KiB
C++
353 lines
11 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
#include "td/tl/tl_jni_object.h"
|
|
|
|
#include "td/utils/common.h"
|
|
#include "td/utils/logging.h"
|
|
#include "td/utils/misc.h"
|
|
#include "td/utils/Slice.h"
|
|
|
|
#include <memory>
|
|
|
|
namespace td {
|
|
namespace jni {
|
|
|
|
thread_local bool parse_error;
|
|
|
|
static jclass BooleanClass;
|
|
static jclass IntegerClass;
|
|
static jclass LongClass;
|
|
static jclass DoubleClass;
|
|
static jclass StringClass;
|
|
static jclass ObjectClass;
|
|
jclass ArrayKeyboardButtonClass;
|
|
jclass ArrayInlineKeyboardButtonClass;
|
|
jmethodID GetConstructorID;
|
|
jmethodID BooleanGetValueMethodID;
|
|
jmethodID IntegerGetValueMethodID;
|
|
jmethodID LongGetValueMethodID;
|
|
jmethodID DoubleGetValueMethodID;
|
|
|
|
static void fatal_error(JNIEnv *env, CSlice error) {
|
|
LOG(ERROR) << error;
|
|
env->FatalError(error.c_str());
|
|
}
|
|
|
|
jclass get_jclass(JNIEnv *env, const char *class_name) {
|
|
jclass clazz = env->FindClass(class_name);
|
|
if (!clazz) {
|
|
fatal_error(env, PSLICE() << "Can't find class [" << class_name << "]");
|
|
}
|
|
jclass clazz_global = (jclass)env->NewGlobalRef(clazz);
|
|
|
|
env->DeleteLocalRef(clazz);
|
|
|
|
if (!clazz_global) {
|
|
fatal_error(env, PSLICE() << "Can't create global reference to [" << class_name << "]");
|
|
}
|
|
|
|
return clazz_global;
|
|
}
|
|
|
|
jmethodID get_method_id(JNIEnv *env, jclass clazz, const char *name, const char *signature) {
|
|
jmethodID res = env->GetMethodID(clazz, name, signature);
|
|
if (!res) {
|
|
fatal_error(env, PSLICE() << "Can't find method [" << name << "] with signature [" << signature << "]");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
jfieldID get_field_id(JNIEnv *env, jclass clazz, const char *name, const char *signature) {
|
|
jfieldID res = env->GetFieldID(clazz, name, signature);
|
|
if (!res) {
|
|
fatal_error(env, PSLICE() << "Can't find field [" << name << "] with signature [" << signature << "]");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void register_native_method(JNIEnv *env, jclass clazz, std::string name, std::string signature, void *function_ptr) {
|
|
JNINativeMethod native_method{&name[0], &signature[0], function_ptr};
|
|
if (env->RegisterNatives(clazz, &native_method, 1) != 0) {
|
|
fatal_error(env, PSLICE() << "RegisterNatives failed for " << name << " with signature " << signature);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<JNIEnv, JvmThreadDetacher> get_jni_env(JavaVM *java_vm, jint jni_version) {
|
|
JNIEnv *env = nullptr;
|
|
if (java_vm->GetEnv(reinterpret_cast<void **>(&env), jni_version) == JNI_EDETACHED) {
|
|
#ifdef JDK1_2 // if not Android JNI
|
|
auto p_env = reinterpret_cast<void **>(&env);
|
|
#else
|
|
auto p_env = &env;
|
|
#endif
|
|
java_vm->AttachCurrentThread(p_env, nullptr);
|
|
} else {
|
|
java_vm = nullptr;
|
|
}
|
|
|
|
return std::unique_ptr<JNIEnv, JvmThreadDetacher>(env, JvmThreadDetacher(java_vm));
|
|
}
|
|
|
|
void init_vars(JNIEnv *env, const char *td_api_java_package) {
|
|
BooleanClass = get_jclass(env, "java/lang/Boolean");
|
|
IntegerClass = get_jclass(env, "java/lang/Integer");
|
|
LongClass = get_jclass(env, "java/lang/Long");
|
|
DoubleClass = get_jclass(env, "java/lang/Double");
|
|
StringClass = get_jclass(env, "java/lang/String");
|
|
ObjectClass = get_jclass(env, (PSLICE() << td_api_java_package << "/TdApi$Object").c_str());
|
|
ArrayKeyboardButtonClass =
|
|
get_jclass(env, (PSLICE() << "[L" << td_api_java_package << "/TdApi$KeyboardButton;").c_str());
|
|
ArrayInlineKeyboardButtonClass =
|
|
get_jclass(env, (PSLICE() << "[L" << td_api_java_package << "/TdApi$InlineKeyboardButton;").c_str());
|
|
GetConstructorID = get_method_id(env, ObjectClass, "getConstructor", "()I");
|
|
BooleanGetValueMethodID = get_method_id(env, BooleanClass, "booleanValue", "()Z");
|
|
IntegerGetValueMethodID = get_method_id(env, IntegerClass, "intValue", "()I");
|
|
LongGetValueMethodID = get_method_id(env, LongClass, "longValue", "()J");
|
|
DoubleGetValueMethodID = get_method_id(env, DoubleClass, "doubleValue", "()D");
|
|
}
|
|
|
|
static size_t get_utf8_from_utf16_length(const jchar *p, jsize len) {
|
|
size_t result = 0;
|
|
for (jsize i = 0; i < len; i++) {
|
|
unsigned int cur = p[i];
|
|
if ((cur & 0xF800) == 0xD800) {
|
|
if (i < len) {
|
|
unsigned int next = p[++i];
|
|
if ((next & 0xFC00) == 0xDC00 && (cur & 0x400) == 0) {
|
|
result += 4;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// TODO wrong UTF-16, it is possible
|
|
return 0;
|
|
}
|
|
result += 1 + (cur >= 0x80) + (cur >= 0x800);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void utf16_to_utf8(const jchar *p, jsize len, char *res) {
|
|
for (jsize i = 0; i < len; i++) {
|
|
unsigned int cur = p[i];
|
|
// TODO conversion unsigned int -> signed char is implementation defined
|
|
if (cur <= 0x7f) {
|
|
*res++ = static_cast<char>(cur);
|
|
} else if (cur <= 0x7ff) {
|
|
*res++ = static_cast<char>(0xc0 | (cur >> 6));
|
|
*res++ = static_cast<char>(0x80 | (cur & 0x3f));
|
|
} else if ((cur & 0xF800) != 0xD800) {
|
|
*res++ = static_cast<char>(0xe0 | (cur >> 12));
|
|
*res++ = static_cast<char>(0x80 | ((cur >> 6) & 0x3f));
|
|
*res++ = static_cast<char>(0x80 | (cur & 0x3f));
|
|
} else {
|
|
// correctness already checked
|
|
unsigned int next = p[++i];
|
|
unsigned int val = ((cur - 0xD800) << 10) + next - 0xDC00 + 0x10000;
|
|
|
|
*res++ = static_cast<char>(0xf0 | (val >> 18));
|
|
*res++ = static_cast<char>(0x80 | ((val >> 12) & 0x3f));
|
|
*res++ = static_cast<char>(0x80 | ((val >> 6) & 0x3f));
|
|
*res++ = static_cast<char>(0x80 | (val & 0x3f));
|
|
}
|
|
}
|
|
}
|
|
|
|
static jsize get_utf16_from_utf8_length(const char *p, size_t len, jsize *surrogates) {
|
|
// UTF-8 correctness is supposed
|
|
jsize result = 0;
|
|
for (size_t i = 0; i < len; i++) {
|
|
result += ((p[i] & 0xc0) != 0x80);
|
|
*surrogates += ((p[i] & 0xf8) == 0xf0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void utf8_to_utf16(const char *p, size_t len, jchar *res) {
|
|
// UTF-8 correctness is supposed
|
|
for (size_t i = 0; i < len;) {
|
|
unsigned int a = static_cast<unsigned char>(p[i++]);
|
|
if (a >= 0x80) {
|
|
unsigned int b = static_cast<unsigned char>(p[i++]);
|
|
if (a >= 0xe0) {
|
|
unsigned int c = static_cast<unsigned char>(p[i++]);
|
|
if (a >= 0xf0) {
|
|
unsigned int d = static_cast<unsigned char>(p[i++]);
|
|
unsigned int val = ((a & 0x07) << 18) + ((b & 0x3f) << 12) + ((c & 0x3f) << 6) + (d & 0x3f) - 0x10000;
|
|
*res++ = static_cast<jchar>(0xD800 + (val >> 10));
|
|
*res++ = static_cast<jchar>(0xDC00 + (val & 0x3ff));
|
|
} else {
|
|
*res++ = static_cast<jchar>(((a & 0x0f) << 12) + ((b & 0x3f) << 6) + (c & 0x3f));
|
|
}
|
|
} else {
|
|
*res++ = static_cast<jchar>(((a & 0x1f) << 6) + (b & 0x3f));
|
|
}
|
|
} else {
|
|
*res++ = static_cast<jchar>(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string fetch_string(JNIEnv *env, jobject o, jfieldID id) {
|
|
jstring s = (jstring)env->GetObjectField(o, id);
|
|
if (s == nullptr) {
|
|
// treat null as an empty string
|
|
return std::string();
|
|
}
|
|
std::string res = from_jstring(env, s);
|
|
env->DeleteLocalRef(s);
|
|
return res;
|
|
}
|
|
|
|
std::string from_jstring(JNIEnv *env, jstring s) {
|
|
if (!s) {
|
|
return "";
|
|
}
|
|
jsize s_len = env->GetStringLength(s);
|
|
const jchar *p = env->GetStringChars(s, nullptr);
|
|
if (p == nullptr) {
|
|
parse_error = true;
|
|
return std::string();
|
|
}
|
|
size_t len = get_utf8_from_utf16_length(p, s_len);
|
|
std::string res(len, '\0');
|
|
if (len) {
|
|
utf16_to_utf8(p, s_len, &res[0]);
|
|
}
|
|
env->ReleaseStringChars(s, p);
|
|
return res;
|
|
}
|
|
|
|
jstring to_jstring(JNIEnv *env, const std::string &s) {
|
|
jsize surrogates = 0;
|
|
jsize unicode_len = get_utf16_from_utf8_length(s.c_str(), s.size(), &surrogates);
|
|
if (surrogates == 0) {
|
|
// TODO '\0'
|
|
return env->NewStringUTF(s.c_str());
|
|
}
|
|
jsize result_len = surrogates + unicode_len;
|
|
if (result_len <= 256) {
|
|
jchar result[256];
|
|
utf8_to_utf16(s.c_str(), s.size(), result);
|
|
return env->NewString(result, result_len);
|
|
}
|
|
|
|
auto result = std::make_unique<jchar[]>(result_len);
|
|
utf8_to_utf16(s.c_str(), s.size(), result.get());
|
|
return env->NewString(result.get(), result_len);
|
|
}
|
|
|
|
std::string from_bytes(JNIEnv *env, jbyteArray arr) {
|
|
std::string b;
|
|
if (arr != nullptr) {
|
|
jsize length = env->GetArrayLength(arr);
|
|
if (length != 0) {
|
|
b.resize(narrow_cast<size_t>(length));
|
|
env->GetByteArrayRegion(arr, 0, length, reinterpret_cast<jbyte *>(&b[0]));
|
|
}
|
|
env->DeleteLocalRef(arr);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
jbyteArray to_bytes(JNIEnv *env, const std::string &b) {
|
|
static_assert(sizeof(char) == sizeof(jbyte), "Mismatched jbyte size");
|
|
jsize length = narrow_cast<jsize>(b.size());
|
|
jbyteArray arr = env->NewByteArray(length);
|
|
if (arr != nullptr && length != 0) {
|
|
env->SetByteArrayRegion(arr, 0, length, reinterpret_cast<const jbyte *>(b.data()));
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
jintArray store_vector(JNIEnv *env, const std::vector<std::int32_t> &v) {
|
|
static_assert(sizeof(std::int32_t) == sizeof(jint), "Mismatched jint size");
|
|
jsize length = narrow_cast<jsize>(v.size());
|
|
jintArray arr = env->NewIntArray(length);
|
|
if (arr != nullptr && length != 0) {
|
|
env->SetIntArrayRegion(arr, 0, length, reinterpret_cast<const jint *>(&v[0]));
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
jlongArray store_vector(JNIEnv *env, const std::vector<std::int64_t> &v) {
|
|
static_assert(sizeof(std::int64_t) == sizeof(jlong), "Mismatched jlong size");
|
|
jsize length = narrow_cast<jsize>(v.size());
|
|
jlongArray arr = env->NewLongArray(length);
|
|
if (arr != nullptr && length != 0) {
|
|
env->SetLongArrayRegion(arr, 0, length, reinterpret_cast<const jlong *>(&v[0]));
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
jdoubleArray store_vector(JNIEnv *env, const std::vector<double> &v) {
|
|
static_assert(sizeof(double) == sizeof(jdouble), "Mismatched jdouble size");
|
|
jsize length = narrow_cast<jsize>(v.size());
|
|
jdoubleArray arr = env->NewDoubleArray(length);
|
|
if (arr != nullptr && length != 0) {
|
|
env->SetDoubleArrayRegion(arr, 0, length, reinterpret_cast<const jdouble *>(&v[0]));
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
jobjectArray store_vector(JNIEnv *env, const std::vector<std::string> &v) {
|
|
jsize length = narrow_cast<jsize>(v.size());
|
|
jobjectArray arr = env->NewObjectArray(length, StringClass, 0);
|
|
if (arr != nullptr) {
|
|
for (jsize i = 0; i < length; i++) {
|
|
jstring str = to_jstring(env, v[i]);
|
|
if (str) {
|
|
env->SetObjectArrayElement(arr, i, str);
|
|
env->DeleteLocalRef(str);
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
std::vector<std::int32_t> fetch_vector(JNIEnv *env, jintArray arr) {
|
|
std::vector<std::int32_t> result;
|
|
if (arr != nullptr) {
|
|
jsize length = env->GetArrayLength(arr);
|
|
if (length != 0) {
|
|
result.resize(length);
|
|
env->GetIntArrayRegion(arr, 0, length, reinterpret_cast<jint *>(&result[0]));
|
|
}
|
|
env->DeleteLocalRef(arr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::int64_t> fetch_vector(JNIEnv *env, jlongArray arr) {
|
|
std::vector<std::int64_t> result;
|
|
if (arr != nullptr) {
|
|
jsize length = env->GetArrayLength(arr);
|
|
if (length != 0) {
|
|
result.resize(length);
|
|
env->GetLongArrayRegion(arr, 0, length, reinterpret_cast<jlong *>(&result[0]));
|
|
}
|
|
env->DeleteLocalRef(arr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<double> fetch_vector(JNIEnv *env, jdoubleArray arr) {
|
|
std::vector<double> result;
|
|
if (arr != nullptr) {
|
|
jsize length = env->GetArrayLength(arr);
|
|
if (length != 0) {
|
|
result.resize(length);
|
|
env->GetDoubleArrayRegion(arr, 0, length, reinterpret_cast<jdouble *>(&result[0]));
|
|
}
|
|
env->DeleteLocalRef(arr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace jni
|
|
} // namespace td
|