diff --git a/example/java/CMakeLists.txt b/example/java/CMakeLists.txt index bcf38f349..680a07e99 100644 --- a/example/java/CMakeLists.txt +++ b/example/java/CMakeLists.txt @@ -8,6 +8,8 @@ endif() project(TdJavaExample VERSION 1.0 LANGUAGES CXX) +option(TD_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.") + if (POLICY CMP0054) # do not expand quoted arguments cmake_policy(SET CMP0054 NEW) @@ -34,26 +36,40 @@ 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() +set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}") +if (TD_JSON_JAVA) + add_custom_target(td_generate_java_api + COMMAND cmake -E echo "" + COMMENT "Skip generation of Java TDLib API source files" + ) -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_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/JsonExample.java") + set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/JsonClient.java") +else() + 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_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}" "${JAVA_SOURCE_PATH}/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_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/Example.java") + set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/Client.java" "${JAVA_SOURCE_PATH}/TdApi.java") +endif() if (CMAKE_VERSION VERSION_LESS "3.17") set(CMAKE_RM_COMMAND remove_directory) @@ -61,9 +77,6 @@ else() set(CMAKE_RM_COMMAND rm -rf --) endif() -set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}") -set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/Example.java") -set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/Client.java" "${JAVA_SOURCE_PATH}/TdApi.java") 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 @@ -86,9 +99,17 @@ 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_link_libraries(tdjni PRIVATE ${JAVA_JVM_LIBRARY}) target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}") +if (TD_JSON_JAVA) + target_link_libraries(tdjni PRIVATE Td::TdJsonStatic) + target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1) + set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava") +else() + target_link_libraries(tdjni PRIVATE Td::TdStatic) +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(GCC 1) elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") diff --git a/example/java/README.md b/example/java/README.md index bca3e7ad9..ca103947f 100644 --- a/example/java/README.md +++ b/example/java/README.md @@ -19,6 +19,8 @@ If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you w In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake +If you want to build JsonClient.java wrapper for JSON interface instead of the native JNI interface, add `-DTD_JSON_JAVA=ON` option to CMake. + After this you can compile the example source code: ``` cd /example/java @@ -28,7 +30,7 @@ cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=/example/j cmake --build . --target install ``` -Compiled TDLib shared library and Java example after that will be placed in bin/ and Javadoc documentation in `docs/`. +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: ``` @@ -36,6 +38,8 @@ cd /example/java/bin java '-Djava.library.path=.' org/drinkless/tdlib/example/Example ``` +If you built JSON interface example using `-DTD_JSON_JAVA=ON` option, then use the command `java '-Djava.library.path=.' org/drinkless/tdlib/example/JsonExample` instead. + 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/`. diff --git a/example/java/org/drinkless/tdlib/JsonClient.java b/example/java/org/drinkless/tdlib/JsonClient.java new file mode 100644 index 000000000..3ffa252dc --- /dev/null +++ b/example/java/org/drinkless/tdlib/JsonClient.java @@ -0,0 +1,79 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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) +// +package org.drinkless.tdlib; + +/** + * Main class for interaction with the TDLib using JSON interface. + */ +public final class JsonClient { + static { + try { + System.loadLibrary("tdjsonjava"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } + } + + /** + * Returns an opaque identifier of a new TDLib instance. + * The TDLib instance will not send updates until the first request is sent to it. + * @return Opaque identifier of a new TDLib instance. + */ + public static native int createClientId(); + + /** + * Sends request to the TDLib client. May be called from any thread. + * @param clientId TDLib client identifier. + * @param request JSON-serialized request. + */ + public static native void send(int clientId, String request); + + /** + * Receives incoming updates and request responses. Must not be called simultaneously from two different threads. + * @param timeout The maximum number of seconds allowed for this function to wait for new data. + * @return JSON-serialized incoming update or request response. May be null if the timeout expired before new data received. + */ + public static native String receive(double timeout); + + /** + * Synchronously executes a TDLib request. + * A request can be executed synchronously, only if it is documented with "Can be called synchronously". + * @param request JSON-serialized request. + * @return JSON-serialized request response. May be null if the request is invalid. + */ + public static native String execute(String request); + + /** + * Interface for handler of messages that are added to the internal TDLib log. + */ + public interface LogMessageHandler { + /** + * Callback called on messages that are added to the internal TDLib log. + * + * @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024. + * If 0, then TDLib will crash as soon as the callback returns. + * None of the TDLib methods can be called from the callback. + * @param message The message added to the internal TDLib log. + */ + void onLogMessage(int verbosityLevel, String message); + } + + /** + * Sets the handler for messages that are added to the internal TDLib log. + * None of the TDLib methods can be called from the callback. + * + * @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called. + * @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler. + */ + public static native void setLogMessageHandler(int maxVerbosityLevel, JsonClient.LogMessageHandler logMessageHandler); + + /** + * The class can't be instantiated. + */ + private JsonClient() { + } +} diff --git a/example/java/org/drinkless/tdlib/example/JsonExample.java b/example/java/org/drinkless/tdlib/example/JsonExample.java new file mode 100644 index 000000000..53e15ad11 --- /dev/null +++ b/example/java/org/drinkless/tdlib/example/JsonExample.java @@ -0,0 +1,47 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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) +// +package org.drinkless.tdlib.example; + +import org.drinkless.tdlib.JsonClient; + +/** + * Example class for TDLib usage from Java using JSON interface. + */ +public final class JsonExample { + public static void main(String[] args) throws InterruptedException { + // set log message handler to handle only fatal errors (0) and plain log messages (-1) + JsonClient.setLogMessageHandler(0, new LogMessageHandler()); + + // disable TDLib log and redirect fatal errors and plain log messages to a file + JsonClient.execute("{\"@type\":\"setLogVerbosityLevel\",\"new_verbosity_level\":0}"); + JsonClient.execute("{\"@type\":\"setLogStream\",\"log_stream\":{\"@type\":\"logStreamFile\",\"path\":\"tdlib.log\",\"max_file_size\":128000000}}"); + + // create client identifier + int clientId = JsonClient.createClientId(); + + // send first request to activate the client + JsonClient.send(clientId, "{\"@type\":\"getOption\",\"name\":\"version\"}"); + + // main loop + while (true) { + String result = JsonClient.receive(100.0); + if (result != null) { + System.out.println(result); + } + } + } + + private static class LogMessageHandler implements JsonClient.LogMessageHandler { + @Override + public void onLogMessage(int verbosityLevel, String message) { + System.err.print(message); + if (verbosityLevel == 0) { + System.err.println("Receive fatal error; the process will crash now"); + } + } + } +} diff --git a/example/java/td_jni.cpp b/example/java/td_jni.cpp index 0271e0575..0fcb6695f 100644 --- a/example/java/td_jni.cpp +++ b/example/java/td_jni.cpp @@ -4,8 +4,12 @@ // 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) // +#ifdef TD_JSON_JAVA +#include +#else #include #include +#endif #include @@ -16,6 +20,31 @@ namespace td_jni { +#ifdef TD_JSON_JAVA +static jint JsonClient_createClientId(JNIEnv *env, jclass clazz) { + return static_cast(td_create_client_id()); +} + +static void JsonClient_send(JNIEnv *env, jclass clazz, jint client_id, jstring request) { + td_send(static_cast(client_id), td::jni::from_jstring(env, request).c_str()); +} + +static jstring JsonClient_receive(JNIEnv *env, jclass clazz, jdouble timeout) { + auto result = td_receive(timeout); + if (result == nullptr) { + return nullptr; + } + return td::jni::to_jstring(env, result); +} + +static jstring JsonClient_execute(JNIEnv *env, jclass clazz, jstring request) { + auto result = td_execute(td::jni::from_jstring(env, request).c_str()); + if (result == nullptr) { + return nullptr; + } + return td::jni::to_jstring(env, result); +} +#else static td::td_api::object_ptr fetch_function(JNIEnv *env, jobject function) { td::jni::reset_parse_error(); auto result = td::td_api::Function::fetch(env, function); @@ -83,6 +112,7 @@ static jstring Object_toString(JNIEnv *env, jobject object) { static jstring Function_toString(JNIEnv *env, jobject object) { return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object))); } +#endif static constexpr jint JAVA_VERSION = JNI_VERSION_1_6; static JavaVM *java_vm; @@ -118,7 +148,11 @@ static void on_log_message(int verbosity_level, const char *log_message) { static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level, jobject new_log_message_handler) { if (log_message_handler) { +#ifdef TD_JSON_JAVA + td_set_log_message_callback(0, nullptr); +#else td::ClientManager::set_log_message_callback(0, nullptr); +#endif jobject old_log_message_handler = log_message_handler; log_message_handler = jobject(); env->DeleteGlobalRef(old_log_message_handler); @@ -131,7 +165,11 @@ static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, j return; } +#ifdef TD_JSON_JAVA + td_set_log_message_callback(static_cast(max_verbosity_level), on_log_message); +#else td::ClientManager::set_log_message_callback(static_cast(max_verbosity_level), on_log_message); +#endif } } @@ -148,6 +186,16 @@ static jint register_native(JavaVM *vm) { reinterpret_cast(function_ptr)); }; +#ifdef TD_JSON_JAVA + auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/JsonClient"); + + register_method(client_class, "createClientId", "()I", JsonClient_createClientId); + register_method(client_class, "send", "(ILjava/lang/String;)V", JsonClient_send); + register_method(client_class, "receive", "(D)Ljava/lang/String;", JsonClient_receive); + register_method(client_class, "execute", "(Ljava/lang/String;)Ljava/lang/String;", JsonClient_execute); + register_method(client_class, "setLogMessageHandler", "(IL" PACKAGE_NAME "/JsonClient$LogMessageHandler;)V", + Client_nativeClientSetLogMessageHandler); +#else 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"); @@ -169,6 +217,7 @@ static jint register_native(JavaVM *vm) { td::jni::init_vars(env, PACKAGE_NAME); td::td_api::get_package_name_ref() = PACKAGE_NAME; +#endif return JAVA_VERSION; }