Add example of JSON interface usage from Java.

This commit is contained in:
levlam 2024-05-31 08:10:40 +03:00
parent 91ac878840
commit f9b7e1bc2e
5 changed files with 222 additions and 22 deletions

View File

@ -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")

View File

@ -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=<VCPKG_DIR>/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 <path to TDLib sources>/example/java
@ -28,7 +30,7 @@ cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/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 <path to TDLib sources>/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/`.

View File

@ -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() {
}
}

View File

@ -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");
}
}
}
}

View File

@ -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 <td/telegram/td_json_client.h>
#else
#include <td/telegram/Client.h>
#include <td/telegram/td_api.h>
#endif
#include <td/tl/tl_jni_object.h>
@ -16,6 +20,31 @@
namespace td_jni {
#ifdef TD_JSON_JAVA
static jint JsonClient_createClientId(JNIEnv *env, jclass clazz) {
return static_cast<jint>(td_create_client_id());
}
static void JsonClient_send(JNIEnv *env, jclass clazz, jint client_id, jstring request) {
td_send(static_cast<int>(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<td::td_api::Function> 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<int>(max_verbosity_level), on_log_message);
#else
td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
#endif
}
}
@ -148,6 +186,16 @@ static jint register_native(JavaVM *vm) {
reinterpret_cast<void *>(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;
}