diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/example/java')
-rw-r--r-- | protocols/Telegram/tdlib/td/example/java/.gitignore | 5 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/example/java/CMakeLists.txt | 124 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/example/java/README.md | 43 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/example/java/td_jni.cpp | 181 |
4 files changed, 353 insertions, 0 deletions
diff --git a/protocols/Telegram/tdlib/td/example/java/.gitignore b/protocols/Telegram/tdlib/td/example/java/.gitignore new file mode 100644 index 0000000000..8f846b80d9 --- /dev/null +++ b/protocols/Telegram/tdlib/td/example/java/.gitignore @@ -0,0 +1,5 @@ +**/*build/ +bin/ +docs/ +org/drinkless/tdlib/TdApi.java +td/ diff --git a/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt new file mode 100644 index 0000000000..576eee2cfc --- /dev/null +++ b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt @@ -0,0 +1,124 @@ +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +if (POLICY CMP0065) + # do not export symbols from executables + # affects compiler checks in project(), so must be set before it + cmake_policy(SET CMP0065 NEW) +endif() + +project(TdJavaExample VERSION 1.0 LANGUAGES CXX) + +if (POLICY CMP0054) + # do not expand quoted arguments + cmake_policy(SET CMP0054 NEW) +endif() +if (POLICY CMP0060) + # link libraries by full path + cmake_policy(SET CMP0060 NEW) +endif() +if (POLICY CMP0074) + # use environment variables to find libraries + cmake_policy(SET CMP0074 NEW) +endif() + +find_package(Td REQUIRED) + +if (NOT JNI_FOUND) + find_package(JNI REQUIRED) +endif() +message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}") + +if (NOT Java_FOUND) + find_package(Java REQUIRED) +endif() +message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}") + +# Generating TdApi.java +find_program(PHP_EXECUTABLE php) +if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD")) + set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND") +endif() + +set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib") +set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +set(TD_API_TLO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tlo) +set(TD_API_TL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tl) +set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/JavadocTlDocumentationGenerator.php) +set(GENERATE_JAVA_API_CMD ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api TdApi ${TD_API_TLO_PATH} ${TD_API_JAVA_PATH} ${TD_API_JAVA_PACKAGE}) +if (PHP_EXECUTABLE) + set(GENERATE_JAVA_API_CMD ${GENERATE_JAVA_API_CMD} && ${PHP_EXECUTABLE} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} ${TD_API_TL_PATH} ${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}/TdApi.java) +endif() + +add_custom_target(td_generate_java_api + COMMAND ${GENERATE_JAVA_API_CMD} + COMMENT "Generating Java TDLib API source files" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api ${TD_API_TLO_PATH} ${TD_API_TL_PATH} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} +) + +set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}") +get_filename_component(JAVA_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") +file(MAKE_DIRECTORY ${JAVA_OUTPUT_DIRECTORY}) +add_custom_target(build_java + COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d ${JAVA_OUTPUT_DIRECTORY} ${JAVA_SOURCE_PATH}/example/Example.java ${JAVA_SOURCE_PATH}/Client.java ${JAVA_SOURCE_PATH}/TdApi.java + COMMENT "Building Java code" + DEPENDS td_generate_java_api +) + +add_custom_target(generate_javadoc + COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib + WORKING_DIRECTORY ${TD_API_JAVA_PATH} + COMMENT "Generating Javadoc documentation" + DEPENDS td_generate_java_api +) + +# Building shared library +add_library(tdjni SHARED + td_jni.cpp +) +target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) +target_link_libraries(tdjni PRIVATE Td::TdStatic ${JAVA_JVM_LIBRARY}) +target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(GCC 1) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CLANG 1) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + set(INTEL 1) +elseif (NOT MSVC) + message(FATAL_ERROR "Compiler isn't supported") +endif() + +include(CheckCXXCompilerFlag) + +if (GCC OR CLANG OR INTEL) + if (WIN32 AND INTEL) + set(STD14_FLAG /Qstd=c++14) + else() + set(STD14_FLAG -std=c++14) + endif() + check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14) + if (NOT HAVE_STD14) + string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}") + check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y) + set(HAVE_STD14 ${HAVE_STD1Y}) + endif() + + target_compile_options(tdjni PRIVATE "${STD14_FLAG}") +elseif (MSVC) + set(HAVE_STD14 MSVC_VERSION>=1900) +endif() + +if (NOT HAVE_STD14) + message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.") +endif() + +add_dependencies(tdjni td_generate_java_api build_java generate_javadoc) + +install(TARGETS tdjni + LIBRARY DESTINATION bin + RUNTIME DESTINATION bin +) +if (MSVC AND VCPKG_TOOLCHAIN) + install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb") +endif() diff --git a/protocols/Telegram/tdlib/td/example/java/README.md b/protocols/Telegram/tdlib/td/example/java/README.md new file mode 100644 index 0000000000..bca3e7ad9c --- /dev/null +++ b/protocols/Telegram/tdlib/td/example/java/README.md @@ -0,0 +1,43 @@ +# TDLib Java example + +To run this example, you will need installed JDK >= 1.6. +For Javadoc documentation generation PHP is needed. + +You can find complete build instructions for your operating system at https://tdlib.github.io/td/build.html?language=Java. + +In general, the build process looks as follows. + +TDLib should be prebuilt with JNI bindings and installed to local subdirectory `td/` as follows: +``` +cd <path to TDLib sources> +mkdir jnibuild +cd jnibuild +cmake -DCMAKE_BUILD_TYPE=Release -DTD_ENABLE_JNI=ON -DCMAKE_INSTALL_PREFIX:PATH=../example/java/td .. +cmake --build . --target install +``` +If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you will also need to add `-A Win32`/`-A x64` option to CMake. + +In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake + +After this you can compile the example source code: +``` +cd <path to TDLib sources>/example/java +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/example/java/td/lib/cmake/Td -DCMAKE_INSTALL_PREFIX:PATH=.. .. +cmake --build . --target install +``` + +Compiled TDLib shared library and Java example after that will be placed in bin/ and Javadoc documentation in `docs/`. + +After this you can run the Java example: +``` +cd <path to TDLib sources>/example/java/bin +java '-Djava.library.path=.' org/drinkless/tdlib/example/Example +``` + +If you receive "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/". + +If you receive java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared OpenSSL and zlib libraries to `bin/`. + +Make sure that you compiled the example for the same architecture as your JVM. diff --git a/protocols/Telegram/tdlib/td/example/java/td_jni.cpp b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp new file mode 100644 index 0000000000..fb7554c747 --- /dev/null +++ b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp @@ -0,0 +1,181 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/telegram/Client.h> +#include <td/telegram/td_api.h> + +#include <td/tl/tl_jni_object.h> + +#include <cstdint> +#include <cstdlib> +#include <string> +#include <utility> + +namespace td_jni { + +static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, jobject function) { + td::jni::reset_parse_error(); + auto result = td::td_api::Function::fetch(env, function); + if (td::jni::have_parse_error()) { + std::abort(); + } + return result; +} + +static td::ClientManager *get_manager() { + return td::ClientManager::get_manager_singleton(); +} + +static jint Client_createNativeClient(JNIEnv *env, jclass clazz) { + return static_cast<jint>(get_manager()->create_client_id()); +} + +static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) { + get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id), + fetch_function(env, function)); +} + +static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids, + jobjectArray events, jdouble timeout) { + jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size + if (events_size == 0) { + return 0; + } + jsize result_size = 0; + + auto *manager = get_manager(); + auto response = manager->receive(timeout); + while (response.object) { + auto client_id = static_cast<jint>(response.client_id); + env->SetIntArrayRegion(client_ids, result_size, 1, &client_id); + + auto request_id = static_cast<jlong>(response.request_id); + env->SetLongArrayRegion(ids, result_size, 1, &request_id); + + jobject object; + response.object->store(env, object); + env->SetObjectArrayElement(events, result_size, object); + env->DeleteLocalRef(object); + + result_size++; + if (result_size == events_size) { + break; + } + + response = manager->receive(0); + } + return result_size; +} + +static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) { + jobject result; + td::ClientManager::execute(fetch_function(env, function))->store(env, result); + return result; +} + +static jstring Object_toString(JNIEnv *env, jobject object) { + return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object))); +} + +static jstring Function_toString(JNIEnv *env, jobject object) { + return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object))); +} + +static constexpr jint JAVA_VERSION = JNI_VERSION_1_6; +static JavaVM *java_vm; +static jobject log_message_handler; + +static void on_log_message(int verbosity_level, const char *log_message) { + auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION); + if (env == nullptr) { + return; + } + + jobject handler = env->NewLocalRef(log_message_handler); + if (!handler) { + return; + } + + jclass handler_class = env->GetObjectClass(handler); + if (handler_class) { + jmethodID on_log_message_method = env->GetMethodID(handler_class, "onLogMessage", "(ILjava/lang/String;)V"); + if (on_log_message_method) { + jstring log_message_str = td::jni::to_jstring(env.get(), log_message); + if (log_message_str) { + env->CallVoidMethod(handler, on_log_message_method, static_cast<jint>(verbosity_level), log_message_str); + env->DeleteLocalRef((jobject)log_message_str); + } + } + env->DeleteLocalRef((jobject)handler_class); + } + + env->DeleteLocalRef(handler); +} + +static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level, + jobject new_log_message_handler) { + if (log_message_handler) { + td::ClientManager::set_log_message_callback(0, nullptr); + jobject old_log_message_handler = log_message_handler; + log_message_handler = jobject(); + env->DeleteGlobalRef(old_log_message_handler); + } + + if (new_log_message_handler) { + log_message_handler = env->NewGlobalRef(new_log_message_handler); + if (!log_message_handler) { + // out of memory + return; + } + + td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message); + } +} + +static jint register_native(JavaVM *vm) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast<void **>(&env), JAVA_VERSION) != JNI_OK) { + return -1; + } + + java_vm = vm; + + auto register_method = [env](jclass clazz, std::string name, std::string signature, auto function_ptr) { + td::jni::register_native_method(env, clazz, std::move(name), std::move(signature), + reinterpret_cast<void *>(function_ptr)); + }; + + auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client"); + auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object"); + auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function"); + +#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;" +#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;" + register_method(client_class, "createNativeClient", "()I", Client_createNativeClient); + register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend); + register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive); + register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute); + register_method(client_class, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V", + Client_nativeClientSetLogMessageHandler); + + register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString); + + register_method(function_class, "toString", "()Ljava/lang/String;", Function_toString); +#undef TD_FUNCTION +#undef TD_OBJECT + + td::jni::init_vars(env, PACKAGE_NAME); + td::td_api::set_package_name(PACKAGE_NAME); + + return JAVA_VERSION; +} + +} // namespace td_jni + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + static jint jni_version = td_jni::register_native(vm); // call_once + return jni_version; +} |