summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rwxr-xr-xMakefile.am2
-rw-r--r--compiler/cpp/CMakeLists.txt1
-rw-r--r--compiler/cpp/Makefile.am1
-rw-r--r--compiler/cpp/compiler.vcxproj3
-rw-r--r--compiler/cpp/compiler.vcxproj.filters3
-rw-r--r--compiler/cpp/src/thrift/generate/t_kotlin_generator.cc2011
-rwxr-xr-xconfigure.ac25
-rw-r--r--lib/Makefile.am5
-rw-r--r--lib/kotlin/.gitignore78
-rw-r--r--lib/kotlin/CMakeLists.txt19
-rw-r--r--lib/kotlin/Makefile.am44
-rw-r--r--lib/kotlin/README.md29
-rw-r--r--lib/kotlin/cross-test-client/build.gradle.kts82
-rw-r--r--lib/kotlin/cross-test-client/src/main/kotlin/org/apache/thrift/test/TestClient.kt1045
-rw-r--r--lib/kotlin/cross-test-client/src/main/resources/logback.xml33
-rw-r--r--lib/kotlin/cross-test-server/build.gradle.kts82
-rw-r--r--lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt260
-rw-r--r--lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt358
-rw-r--r--lib/kotlin/cross-test-server/src/main/resources/logback.xml32
-rw-r--r--lib/kotlin/settings.gradle.kts24
-rw-r--r--test/crossrunner/run.py6
-rw-r--r--test/known_failures_Linux.json2
-rw-r--r--test/tests.json35
24 files changed, 4181 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e5f93cdeb..7119628b3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -113,6 +113,10 @@ if(BUILD_JAVASCRIPT)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/js)
endif()
+if(BUILD_KOTLIN)
+ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/kotlin)
+endif()
+
if(BUILD_NODEJS)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/nodejs)
endif()
diff --git a/Makefile.am b/Makefile.am
index fff2219d9..c3e5f8a0d 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,7 +78,7 @@ empty :=
space := $(empty) $(empty)
comma := ,
-CROSS_LANGS = @MAYBE_CPP@ @MAYBE_C_GLIB@ @MAYBE_D@ @MAYBE_JAVA@ @MAYBE_PYTHON@ @MAYBE_PY3@ @MAYBE_RUBY@ @MAYBE_PERL@ @MAYBE_PHP@ @MAYBE_GO@ @MAYBE_NODEJS@ @MAYBE_DART@ @MAYBE_ERLANG@ @MAYBE_LUA@ @MAYBE_RS@ @MAYBE_NETSTD@ @MAYBE_NODETS@
+CROSS_LANGS = @MAYBE_CPP@ @MAYBE_C_GLIB@ @MAYBE_D@ @MAYBE_JAVA@ @MAYBE_PYTHON@ @MAYBE_PY3@ @MAYBE_RUBY@ @MAYBE_PERL@ @MAYBE_PHP@ @MAYBE_GO@ @MAYBE_NODEJS@ @MAYBE_DART@ @MAYBE_ERLANG@ @MAYBE_LUA@ @MAYBE_RS@ @MAYBE_NETSTD@ @MAYBE_NODETS@ @MAYBE_KOTLIN@
CROSS_LANGS_COMMA_SEPARATED = $(subst $(space),$(comma),$(CROSS_LANGS))
if WITH_PY3
diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt
index 739924a43..717b645ba 100644
--- a/compiler/cpp/CMakeLists.txt
+++ b/compiler/cpp/CMakeLists.txt
@@ -87,6 +87,7 @@ THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON)
THRIFT_ADD_COMPILER(javame "Enable compiler for Java ME" ON)
THRIFT_ADD_COMPILER(js "Enable compiler for JavaScript" ON)
THRIFT_ADD_COMPILER(json "Enable compiler for JSON" ON)
+THRIFT_ADD_COMPILER(kotlin "Enable compiler for Kotlin" ON)
THRIFT_ADD_COMPILER(lua "Enable compiler for Lua" ON)
THRIFT_ADD_COMPILER(netstd "Enable compiler for .NET Standard" ON)
THRIFT_ADD_COMPILER(ocaml "Enable compiler for OCaml" ON)
diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am
index d54f79cdc..cc776ef5e 100644
--- a/compiler/cpp/Makefile.am
+++ b/compiler/cpp/Makefile.am
@@ -84,6 +84,7 @@ thrift_SOURCES += src/thrift/generate/t_c_glib_generator.cc \
src/thrift/generate/t_javame_generator.cc \
src/thrift/generate/t_js_generator.cc \
src/thrift/generate/t_json_generator.cc \
+ src/thrift/generate/t_kotlin_generator.cc \
src/thrift/generate/t_lua_generator.cc \
src/thrift/generate/t_netstd_generator.cc \
src/thrift/generate/t_netstd_generator.h \
diff --git a/compiler/cpp/compiler.vcxproj b/compiler/cpp/compiler.vcxproj
index 4191a47c2..ae77f3d0c 100644
--- a/compiler/cpp/compiler.vcxproj
+++ b/compiler/cpp/compiler.vcxproj
@@ -69,6 +69,7 @@
<ClCompile Include="src\thrift\generate\t_javame_generator.cc" />
<ClCompile Include="src\thrift\generate\t_js_generator.cc" />
<ClCompile Include="src\thrift\generate\t_json_generator.cc" />
+ <ClCompile Include="src\thrift\generate\t_kotlin_generator.cc" />
<ClCompile Include="src\thrift\generate\t_lua_generator.cc" />
<ClCompile Include="src\thrift\generate\t_netstd_generator.cc" />
<ClCompile Include="src\thrift\generate\t_ocaml_generator.cc" />
@@ -246,4 +247,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project> \ No newline at end of file
+</Project>
diff --git a/compiler/cpp/compiler.vcxproj.filters b/compiler/cpp/compiler.vcxproj.filters
index 546d0fdc6..1c97e28bd 100644
--- a/compiler/cpp/compiler.vcxproj.filters
+++ b/compiler/cpp/compiler.vcxproj.filters
@@ -140,6 +140,9 @@
<ClCompile Include="src\generate\t_js_generator.cc">
<Filter>generate</Filter>
</ClCompile>
+ <ClCompile Include="src\generate\t_kotlin_generator.cc">
+ <Filter>generate</Filter>
+ </ClCompile>
<ClCompile Include="src\generate\t_ocaml_generator.cc">
<Filter>generate</Filter>
</ClCompile>
diff --git a/compiler/cpp/src/thrift/generate/t_kotlin_generator.cc b/compiler/cpp/src/thrift/generate/t_kotlin_generator.cc
new file mode 100644
index 000000000..21f13a816
--- /dev/null
+++ b/compiler/cpp/src/thrift/generate/t_kotlin_generator.cc
@@ -0,0 +1,2011 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <cassert>
+#include <ctime>
+
+#include <cctype>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <stdexcept>
+#include <sys/stat.h>
+
+#include "thrift/generate/t_oop_generator.h"
+#include "thrift/platform.h"
+
+using std::map;
+using std::ostream;
+using std::ostringstream;
+using std::set;
+using std::setfill;
+using std::setw;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+static const string endl = "\n"; // avoid ostream << std::endl flushes
+
+static const string KOTLIN_RESERVED_WORDS[] = {
+ "as", "as?", "break", "class", "continue", "do", "else",
+ "false", "for", "fun", "if", "in", "!in", "interface",
+ "is", "!is", "null", "object", "package", "return", "super",
+ "this", "throw", "true", "try", "typealias", "typeof", "val",
+ "var", "when", "while", "by", "catch", "constructor", "delegate",
+ "dynamic", "field", "file", "finally", "get", "import", "init",
+ "param", "property", "receiver", "set", "setparam", "value", "where",
+};
+
+const set<string> KOTLIN_RESERVED_WORDS_SET(KOTLIN_RESERVED_WORDS,
+ KOTLIN_RESERVED_WORDS
+ + sizeof(KOTLIN_RESERVED_WORDS)
+ / sizeof(KOTLIN_RESERVED_WORDS[0]));
+
+/**
+ * Kotlin code generator.
+ */
+class t_kotlin_generator : public t_oop_generator {
+public:
+ t_kotlin_generator(t_program* program,
+ const std::map<std::string, std::string>& /*parsed_options*/,
+ const std::string& /*option_string*/)
+ : t_oop_generator(program) {}
+
+ /**
+ * Init and close methods
+ */
+ void init_generator() override;
+ void close_generator() override;
+
+ void generate_consts(std::vector<t_const*> consts) override;
+
+ /**
+ * Program-level generation functions
+ */
+
+ void generate_typedef(t_typedef* ttypedef) override;
+ void generate_enum(t_enum* tenum) override;
+ void generate_struct(t_struct* tstruct) override;
+ // void generate_union(t_struct* tunion);
+ void generate_xception(t_struct* txception) override;
+ void generate_service(t_service* tservice) override;
+
+private:
+ std::string package_name_;
+ std::string package_dir_;
+ ofstream_with_content_based_conditional_update f_extensions_;
+ ofstream_with_content_based_conditional_update f_types_;
+
+ std::string kotlin_package();
+ std::string warning_surpressions();
+ std::string constant_name(std::string name);
+ std::string type_to_enum(t_type* type);
+ std::string inner_enum_type_name(t_type* ttype);
+ bool is_enum_set(t_type* ttype);
+ bool is_enum_map(t_type* ttype);
+ std::string type_name(t_type* ttype,
+ bool in_init = false,
+ bool skip_generic = false,
+ bool force_namespace = false);
+ std::string base_type_name(t_base_type* tbase);
+ std::string function_signature(t_function* tfunction, std::string prefix = "");
+
+ std::string base_type_write_expression(t_base_type* tbase, std::string it = "it");
+ std::string base_type_read_expression(t_base_type* tbase);
+
+ bool is_reserved(const string& name);
+
+ string kotlin_safe_name(const string& name);
+
+ void generate_kdoc_comment(std::ostream& out, t_doc* tdoc);
+
+ void generate_kotlin_struct(t_struct* tstruct, bool is_exception);
+
+ void generate_kotlin_ext_definition(std::ostream& out, std::string type_name);
+ void generate_kotlin_ext_field_definition(std::ostream& out);
+ void generate_kotlin_ext_map_definition(std::ostream& out);
+ void generate_kotlin_ext_container_definition(std::ostream& out, std::string type_name);
+
+ void generate_service_interface(t_service* tservice);
+ void generate_service_client(t_service* tservice);
+ void generate_client_call(std::ostream& out, t_service* tservice, t_function* tfunc);
+ void generate_service_processor(t_service* tservice);
+ void generate_service_process_function(ostream& out, t_service* tservice, t_function* tfunc);
+
+ void generate_service_args_helpers(t_service* tservice);
+ void generate_service_result_helpers(t_service* tservice);
+
+ void generate_union_definition(std::ostream& out,
+ t_struct* tunion,
+ std::string additional_interface = "");
+ void generate_union_standard_scheme(std::ostream& out, t_struct* tunion);
+ void generate_union_tuple_scheme(std::ostream& out, t_struct* tunion);
+ void generate_union_standard_scheme_read(std::ostream& out, t_struct* tunion);
+ void generate_union_standard_scheme_write(std::ostream& out, t_struct* tunion);
+ void generate_union_methods_definitions(std::ostream& out, t_struct* tunion);
+ void generate_union_method_check_type(std::ostream& out, t_struct* tunion);
+
+ void generate_struct_definition(std::ostream& out,
+ t_struct* tstruct,
+ bool is_xception = false,
+ std::string additional_interface = "");
+ void generate_struct_field_name_constants(std::ostream& out, t_struct* tstruct);
+ void generate_struct_companion_object(std::ostream& out, t_struct* tstruct);
+ void generate_struct_standard_scheme(std::ostream& out, t_struct* tstruct);
+ void generate_struct_standard_scheme_read(std::ostream& out, t_struct* tstruct);
+ void generate_struct_standard_scheme_write(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_deep_copy(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_compare_to(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_field_for_id(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_set_field_value(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_get_field_value(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_is_set(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_clear(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_validate(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_read(std::ostream& out, t_struct* tstruct);
+ void generate_struct_method_write(std::ostream& out, t_struct* tstruct);
+
+ void generate_serialize_value(ostream& out, t_type* ttype, std::string it = "it");
+ void generate_serialize_field(ostream& out, t_field* tfield);
+ void generate_serialize_container(ostream& out, t_type* ttype, std::string it = "it");
+
+ void generate_deserialize_value(ostream& out, t_type* ttype);
+ void generate_deserialize_field(ostream& out, t_field* tfield, string prefix);
+ void generate_deserialize_container(ostream& out, t_type* ttype);
+
+ void generate_kotlin_union(t_struct* tstruct);
+};
+
+/**
+ * Prepares for file generation by opening up the necessary file output
+ * streams.
+ *
+ * @param tprogram The program to generate
+ */
+void t_kotlin_generator::init_generator() {
+ // Make output directory
+ MKDIR(get_out_dir().c_str());
+ package_name_ = program_->get_namespace("java");
+ string dir = package_name_;
+ string subdir = get_out_dir();
+ string::size_type loc;
+ while ((loc = dir.find(".")) != string::npos) {
+ subdir = subdir + "/" + dir.substr(0, loc);
+ MKDIR(subdir.c_str());
+ dir = dir.substr(loc + 1);
+ }
+ if (dir.size() > 0) {
+ subdir = subdir + "/" + dir;
+ MKDIR(subdir.c_str());
+ }
+
+ package_dir_ = subdir;
+
+ string f_types_name = package_dir_ + "/" + program_->get_name() + "Constants.kt";
+ f_types_.open(f_types_name);
+ f_types_ << autogen_comment() << kotlin_package();
+
+ string f_extensions_name = package_dir_ + "/TProtocolExt.kt";
+ f_extensions_.open(f_extensions_name);
+ f_extensions_ << autogen_comment() << kotlin_package();
+
+ generate_kotlin_ext_definition(f_extensions_, "Message");
+ generate_kotlin_ext_definition(f_extensions_, "Struct");
+ generate_kotlin_ext_field_definition(f_extensions_);
+ generate_kotlin_ext_map_definition(f_extensions_);
+ generate_kotlin_ext_container_definition(f_extensions_, "Set");
+ generate_kotlin_ext_container_definition(f_extensions_, "List");
+}
+
+/**
+ * Nothing in Kotlin generator
+ */
+void t_kotlin_generator::close_generator() {
+ f_types_ << endl;
+ f_types_.close();
+ f_extensions_ << endl;
+ f_extensions_.close();
+}
+
+void t_kotlin_generator::generate_kotlin_ext_definition(std::ostream& out, std::string type_name) {
+ out << "internal inline fun org.apache.thrift.protocol.TProtocol.write" << type_name
+ << "(marker: "
+ "org.apache.thrift.protocol.T"
+ << type_name << ", action: () -> Unit) {" << endl;
+ indent_up();
+ indent(out) << "write" << type_name << "Begin(marker)" << endl;
+ indent(out) << "try { action() }" << endl;
+ indent(out) << "finally { write" << type_name << "End() }" << endl;
+ scope_down(out);
+ out << endl;
+
+ out << "internal inline fun <R> org.apache.thrift.protocol.TProtocol.read" << type_name
+ << "(action: org.apache.thrift.protocol.T" << type_name << ".() -> R): R {" << endl;
+ indent_up();
+ indent(out) << "val marker = read" << type_name << "Begin()" << endl;
+ indent(out) << "try { return action(marker) }" << endl;
+ indent(out) << "finally { read" << type_name << "End() }" << endl;
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_kotlin_ext_field_definition(std::ostream& out) {
+ out << "internal inline fun org.apache.thrift.protocol.TProtocol.writeField(marker: "
+ "org.apache.thrift.protocol.TField, action: () -> Unit) {"
+ << endl;
+ indent_up();
+ indent(out) << "writeFieldBegin(marker)" << endl;
+ indent(out) << "try { action() }" << endl;
+ indent(out) << "finally { writeFieldEnd() }" << endl;
+ scope_down(out);
+ out << endl;
+
+ out << "internal inline fun org.apache.thrift.protocol.TProtocol.readField(action: "
+ "org.apache.thrift.protocol.TField.() -> kotlin.Unit): kotlin.Boolean {"
+ << endl;
+ indent_up();
+ indent(out) << "val marker = readFieldBegin()" << endl;
+ indent(out) << "if (marker.type == org.apache.thrift.protocol.TType.STOP) { return true }"
+ << endl;
+ indent(out) << "try {" << endl;
+ indent_up();
+ indent(out) << "action(marker)" << endl;
+ indent(out) << "return false" << endl;
+ indent_down();
+ indent(out) << "} finally { readFieldEnd() }" << endl;
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_kotlin_ext_map_definition(std::ostream& out) {
+ out << "internal inline fun <K, V> org.apache.thrift.protocol.TProtocol.writeMap(keyType: "
+ "kotlin.Byte, valueType: kotlin.Byte, map: Map<K, V>, action: (Map.Entry<K, V>) -> "
+ "Unit) {"
+ << endl;
+ indent_up();
+ indent(out) << "writeMapBegin(org.apache.thrift.protocol.TMap(keyType, valueType, map.size))"
+ << endl;
+ indent(out) << "map.forEach { action(it) }" << endl;
+ indent(out) << "writeMapEnd()" << endl;
+ scope_down(out);
+ out << endl;
+ out << "internal inline fun <R> org.apache.thrift.protocol.TProtocol.readMap(action: "
+ "org.apache.thrift.protocol.TMap.() -> R): R {"
+ << endl;
+ indent_up();
+ indent(out) << "val marker = readMapBegin()" << endl;
+ indent(out) << "val r = action(marker)" << endl;
+ indent(out) << "readMapEnd()" << endl;
+ indent(out) << "return r" << endl;
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_kotlin_ext_container_definition(std::ostream& out,
+ std::string type_name) {
+ out << "internal inline fun <T> org.apache.thrift.protocol.TProtocol.write" << type_name
+ << "(elemType: kotlin.Byte, container: " << type_name << "<T>, action: (T) -> Unit) {"
+ << endl;
+ indent_up();
+ indent(out) << "write" << type_name << "Begin(org.apache.thrift.protocol.T" << type_name
+ << "(elemType, container.size))" << endl;
+ indent(out) << "container.forEach { action(it) }" << endl;
+ indent(out) << "write" << type_name << "End()" << endl;
+ scope_down(out);
+ out << endl;
+ out << "internal inline fun <R> org.apache.thrift.protocol.TProtocol.read" << type_name
+ << "(action: org.apache.thrift.protocol.T" << type_name << ".() -> R): R {" << endl;
+ indent_up();
+ indent(out) << "val marker = read" << type_name << "Begin()" << endl;
+ indent(out) << "val r = action(marker)" << endl;
+ indent(out) << "read" << type_name << "End()" << endl;
+ indent(out) << "return r" << endl;
+ scope_down(out);
+ out << endl;
+}
+
+/**
+ * Generates a typedef. This is not done in Java, since it does
+ * not support arbitrary name replacements, and it'd be a wacky waste
+ * of overhead to make wrapper classes.
+ *
+ * @param ttypedef The type definition
+ */
+void t_kotlin_generator::generate_typedef(t_typedef* ttypedef) {
+ f_types_ << "typealias " << ttypedef->get_symbolic() << " = "
+ << type_name(ttypedef->get_type(), true) << endl;
+}
+
+void t_kotlin_generator::generate_enum(t_enum* tenum) {
+ // Make output file
+ string f_enum_name = package_dir_ + "/" + (tenum->get_name()) + ".kt";
+ ofstream_with_content_based_conditional_update f_enum;
+ f_enum.open(f_enum_name.c_str());
+
+ f_enum << autogen_comment() << kotlin_package();
+
+ indent(f_enum) << "enum class " << kotlin_safe_name(tenum->get_name())
+ << "(private val value: kotlin.Int) : org.apache.thrift.TEnum {";
+ indent_up();
+ indent(f_enum);
+
+ auto first = true;
+ auto enum_values = tenum->get_constants();
+ for (auto& enum_value : enum_values) {
+ f_enum << (first ? "" : ",") << endl;
+ first = false;
+ indent(f_enum) << enum_value->get_name() << "(" << enum_value->get_value() << ")";
+ }
+ if (first) {
+ indent(f_enum);
+ }
+ f_enum << ";" << endl << endl;
+ indent(f_enum) << "override fun getValue() = value" << endl << endl;
+ {
+ indent(f_enum) << "companion object {" << endl;
+ indent_up();
+ {
+ indent(f_enum) << "@kotlin.jvm.JvmStatic" << endl;
+ indent(f_enum) << "fun findByValue(i: kotlin.Int): " << kotlin_safe_name(tenum->get_name())
+ << "? {" << endl;
+ indent_up();
+ {
+ indent(f_enum) << "return when (i) {" << endl;
+ indent_up();
+ {
+ auto enum_values = tenum->get_constants();
+ for (auto& enum_value : enum_values) {
+ indent(f_enum) << enum_value->get_value() << " -> " << enum_value->get_name() << endl;
+ }
+ indent(f_enum) << "else -> null" << endl;
+ }
+ scope_down(f_enum);
+ }
+ scope_down(f_enum);
+ }
+ scope_down(f_enum);
+ }
+ scope_down(f_enum);
+ f_enum.close();
+}
+
+void t_kotlin_generator::generate_consts(std::vector<t_const*> consts) {
+ for (auto const_value : consts) {
+ auto const_type = const_value->get_type();
+ if (const_type->is_base_type()) {
+ f_types_ << "const ";
+ }
+ f_types_ << "val " << kotlin_safe_name(const_value->get_name()) << ": " << type_name(const_type)
+ << " = ";
+
+ auto value = const_value->get_value();
+ if (const_type->is_base_type()) {
+ t_base_type::t_base tbase = ((t_base_type*)const_type)->get_base();
+ switch (tbase) {
+ case t_base_type::TYPE_STRING:
+ f_types_ << "\"" << value->get_string() << "\"";
+ break;
+ case t_base_type::TYPE_BOOL:
+ f_types_ << ((value->get_integer() > 0) ? "true" : "false");
+ break;
+ case t_base_type::TYPE_I8:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ f_types_ << value->get_integer();
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ if (value->get_type() == t_const_value::CV_INTEGER) {
+ f_types_ << value->get_integer() << ".";
+ } else {
+ f_types_ << emit_double_as_string(value->get_double());
+ }
+ break;
+ default:
+ f_types_ << value->get_integer();
+ break;
+ }
+ } else if (const_type->is_enum()) {
+ auto namespace_prefix = const_type->get_program()->get_namespace("java");
+ if (namespace_prefix.length() > 0) {
+ namespace_prefix += ".";
+ }
+ f_types_ << namespace_prefix + value->get_identifier_with_parent();
+ } else {
+ // TODO
+ }
+ f_types_ << endl;
+ }
+}
+
+string t_kotlin_generator::base_type_name(t_base_type* type) {
+ t_base_type::t_base tbase = type->get_base();
+
+ switch (tbase) {
+ case t_base_type::TYPE_VOID:
+ return "kotlin.Unit";
+ case t_base_type::TYPE_STRING:
+ if (type->is_binary()) {
+ return "kotlin.ByteArray";
+ } else {
+ return "kotlin.String";
+ }
+ case t_base_type::TYPE_BOOL:
+ return "kotlin.Boolean";
+ case t_base_type::TYPE_I8:
+ return "kotlin.Byte";
+ case t_base_type::TYPE_I16:
+ return "kotlin.Short";
+ case t_base_type::TYPE_I32:
+ return "kotlin.Int";
+ case t_base_type::TYPE_I64:
+ return "kotlin.Long";
+ case t_base_type::TYPE_DOUBLE:
+ return "kotlin.Double";
+ default:
+ throw "compiler error: no Kotlin name for base type " + t_base_type::t_base_name(tbase);
+ }
+}
+
+string t_kotlin_generator::type_name(t_type* ttype,
+ bool in_init,
+ bool skip_generic,
+ bool force_namespace) {
+ ttype = get_true_type(ttype);
+ string prefix;
+ if (ttype->is_base_type()) {
+ return base_type_name((t_base_type*)ttype);
+ } else if (ttype->is_map()) {
+ t_map* tmap = (t_map*)ttype;
+ if (in_init) {
+ prefix = "kotlin.collections.Map";
+ } else {
+ prefix = "kotlin.collections.Map";
+ }
+ return prefix
+ + (skip_generic ? ""
+ : "<" + type_name(tmap->get_key_type(), true) + ", "
+ + type_name(tmap->get_val_type(), true) + ">");
+ } else if (ttype->is_set()) {
+ t_set* tset = (t_set*)ttype;
+ if (in_init) {
+ prefix = "kotlin.collections.Set";
+ } else {
+ prefix = "kotlin.collections.Set";
+ }
+ return prefix + (skip_generic ? "" : "<" + type_name(tset->get_elem_type(), true) + ">");
+ } else if (ttype->is_list()) {
+ t_list* tlist = (t_list*)ttype;
+ if (in_init) {
+ prefix = "kotlin.collections.List";
+ } else {
+ prefix = "kotlin.collections.List";
+ }
+ return prefix + (skip_generic ? "" : "<" + type_name(tlist->get_elem_type(), true) + ">");
+ }
+
+ // Check for namespacing
+ t_program* program = ttype->get_program();
+ if ((program != nullptr) && ((program != program_) || force_namespace)) {
+ string package = program->get_namespace("java");
+ if (!package.empty()) {
+ return package + "." + kotlin_safe_name(ttype->get_name());
+ }
+ }
+
+ return kotlin_safe_name(ttype->get_name());
+}
+
+/**
+ * Generates a struct definition for a thrift data type. This will be a org.apache.thrift.TBase
+ * implementor.
+ *
+ * @param tstruct The struct definition
+ */
+void t_kotlin_generator::generate_struct(t_struct* tstruct) {
+ if (tstruct->is_union()) {
+ generate_kotlin_union(tstruct);
+ } else {
+ generate_kotlin_struct(tstruct, false);
+ }
+}
+
+void t_kotlin_generator::generate_kotlin_union(t_struct* tunion) {
+ string f_union_name = package_dir_ + "/" + (tunion->get_name()) + ".kt";
+ ofstream_with_content_based_conditional_update f_union;
+ f_union.open(f_union_name.c_str());
+ f_union << autogen_comment() << warning_surpressions() << kotlin_package();
+ generate_union_definition(f_union, tunion);
+ f_union.close();
+}
+
+void t_kotlin_generator::generate_kotlin_struct(t_struct* tstruct, bool is_exception) {
+ string f_struct_name = package_dir_ + "/" + (tstruct->get_name()) + ".kt";
+ ofstream_with_content_based_conditional_update f_struct;
+ f_struct.open(f_struct_name.c_str());
+ f_struct << autogen_comment() << warning_surpressions() << kotlin_package();
+ generate_struct_definition(f_struct, tstruct, is_exception);
+ f_struct.close();
+}
+
+void t_kotlin_generator::generate_struct_field_name_constants(std::ostream& out,
+ t_struct* tstruct) {
+ indent(out) << "enum class _Fields(private val thriftFieldId: kotlin.Short, private val "
+ "fieldName: kotlin.String) : org.apache.thrift.TFieldIdEnum {"
+ << endl;
+ indent_up();
+ {
+ // fields
+ {
+ bool first = true;
+ for (auto& field : tstruct->get_members()) {
+ if (!first) {
+ out << "," << endl;
+ }
+ first = false;
+ indent(out) << constant_name(field->get_name()) << "(" << field->get_key() << ", \""
+ << field->get_name() << "\")";
+ }
+ if (first) {
+ indent(out);
+ }
+ out << ";" << endl << endl;
+ }
+
+ // methods
+ indent(out) << "override fun getThriftFieldId() = thriftFieldId" << endl << endl;
+ indent(out) << "override fun getFieldName() = fieldName" << endl << endl;
+
+ // companion object
+ indent(out) << "companion object {" << endl;
+ indent_up();
+ {
+ indent(out) << "@kotlin.jvm.JvmStatic" << endl;
+ indent(out) << "fun findByValue(value: kotlin.Int): _Fields? {" << endl;
+ indent_up();
+ {
+ indent(out) << "return when (value) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << field->get_key() << " -> " << constant_name(field->get_name()) << endl;
+ }
+ indent(out) << "else -> null" << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+
+ out << endl;
+
+ {
+ indent(out) << "@kotlin.jvm.JvmStatic" << endl;
+ indent(out) << "fun findByName(name: kotlin.String): _Fields? {" << endl;
+ indent_up();
+ {
+ indent(out) << "return when (name) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << "\"" << field->get_name() << "\""
+ << " -> " << constant_name(field->get_name()) << endl;
+ }
+ indent(out) << "else -> null" << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+
+ scope_down(out);
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_companion_object(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "companion object {" << endl;
+ indent_up();
+ {
+ indent(out) << "private val STRUCT_DESC: org.apache.thrift.protocol.TStruct = "
+ "org.apache.thrift.protocol.TStruct(\""
+ << tstruct->get_name() << "\")" << endl;
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << "private val " << constant_name(field->get_name())
+ << "_FIELD_DESC: org.apache.thrift.protocol.TField = "
+ "org.apache.thrift.protocol.TField(\""
+ << field->get_name() << "\", " << type_to_enum(field->get_type()) << ", "
+ << field->get_key() << ")" << endl;
+ }
+ }
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_deep_copy(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun deepCopy(): " << tstruct->get_name() << " {" << endl;
+ indent_up();
+ {
+ indent(out) << "return " << tstruct->get_name() << " (" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << kotlin_safe_name(field->get_name()) << "," << endl;
+ }
+ }
+ indent_down();
+ indent(out) << ")" << endl;
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_compare_to(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun compareTo(other: " << tstruct->get_name() << "?): kotlin.Int {"
+ << endl;
+ indent_up();
+ {
+ indent(out) << "val comparator = compareBy<" << tstruct->get_name()
+ << "> { it::class.java.name }" << endl;
+ indent_up();
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << ".thenBy";
+ auto field_type = field->get_type();
+ if (field_type->is_list() || field_type->is_set() || field_type->is_map()
+ || field_type->is_binary()) {
+ out << "(org.apache.thrift.TBaseHelper::compareTo)";
+ }
+ out << " { it." << kotlin_safe_name(field->get_name()) << " } " << endl;
+ }
+ indent_down();
+ indent(out) << "return nullsFirst(comparator).compare(this, other)" << endl;
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_field_for_id(std::ostream& out,
+ t_struct* /*tstruct*/) {
+ indent(out) << "override fun fieldForId(fieldId: kotlin.Int): _Fields {" << endl;
+ indent_up();
+ {
+ indent(out) << "return _Fields.findByValue(fieldId) ?: throw "
+ "kotlin.IllegalArgumentException(\"invalid fieldId $fieldId\")"
+ << endl;
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_is_set(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun isSet(field: _Fields): kotlin.Boolean {" << endl;
+ indent_up();
+ {
+ indent(out) << "return when (field) {" << endl;
+ indent_up();
+ {
+ auto members = tstruct->get_members();
+ if (members.size() > 0) {
+ for (auto& field : members) {
+ indent(out) << "_Fields." << constant_name(field->get_name()) << " -> ";
+ if (field->get_req() == t_field::T_REQUIRED) {
+ out << "this._" << field->get_name() << " != null";
+ } else {
+ out << "this." << kotlin_safe_name(field->get_name()) << " != null";
+ }
+ out << endl;
+ }
+ } else {
+ indent(out) << "else -> false" << endl;
+ }
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_clear(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun clear(): kotlin.Unit {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ auto is_required = field->get_req() == t_field::T_REQUIRED;
+ indent(out) << (is_required ? "_" + field->get_name() : kotlin_safe_name(field->get_name()))
+ << " = null" << endl;
+ }
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_validate(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "@kotlin.jvm.Throws(org.apache.thrift.TException::class)" << endl;
+ indent(out) << "fun validate(): kotlin.Unit {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ bool is_required = field->get_req() == t_field::T_REQUIRED;
+ if (is_required) {
+ indent(out) << "if (_" << field->get_name() << " == null) {" << endl;
+ indent_up();
+ {
+ indent(out) << "throw org.apache.thrift.TException(\"Required field `"
+ << field->get_name()
+ << "' is null, "
+ "struct is: $this\")"
+ << endl;
+ }
+ scope_down(out);
+ }
+ }
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_set_field_value(std::ostream& out,
+ t_struct* tstruct) {
+ indent(out) << "@Suppress(\"UNCHECKED_CAST\")" << endl;
+ indent(out) << "override fun setFieldValue(field: _Fields, value: kotlin.Any?): kotlin.Unit {"
+ << endl;
+ indent_up();
+ {
+ const vector<t_field*>& members = tstruct->get_members();
+ if (members.size() > 0) {
+ indent(out) << "when (field) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ auto is_required = field->get_req() == t_field::T_REQUIRED;
+ indent(out) << "_Fields." << constant_name(field->get_name()) << " -> this."
+ << (is_required ? "_" + field->get_name()
+ : kotlin_safe_name(field->get_name()))
+ << " = value as " << type_name(field->get_type()) << "?" << endl;
+ }
+ }
+ scope_down(out);
+ } else {
+ indent(out) << "return" << endl;
+ }
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_get_field_value(std::ostream& out,
+ t_struct* tstruct) {
+ indent(out) << "override fun getFieldValue(field: _Fields): kotlin.Any? {" << endl;
+ indent_up();
+ {
+ auto members = tstruct->get_members();
+ if (members.size() > 0) {
+ indent(out) << "return when (field) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << "_Fields." << constant_name(field->get_name()) << " -> this."
+ << kotlin_safe_name(field->get_name()) << endl;
+ }
+ }
+ scope_down(out);
+ } else {
+ indent(out) << "return null" << endl;
+ }
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_method_read(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun read(iproto: org.apache.thrift.protocol.TProtocol): kotlin.Unit {"
+ << endl;
+ indent_up();
+ {
+ indent(out)
+ << "require(org.apache.thrift.scheme.StandardScheme::class.java == iproto.scheme) { "
+ "\"only standard scheme is "
+ "supported for now\" }"
+ << endl;
+ indent(out) << tstruct->get_name() << "StandardScheme.read(iproto, this)" << endl;
+ }
+ scope_down(out);
+ out << endl;
+}
+void t_kotlin_generator::generate_struct_method_write(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "override fun write(oproto: org.apache.thrift.protocol.TProtocol): kotlin.Unit {"
+ << endl;
+ indent_up();
+ {
+ indent(out)
+ << "require(org.apache.thrift.scheme.StandardScheme::class.java == oproto.scheme) { "
+ "\"only standard scheme is "
+ "supported for now\" }"
+ << endl;
+ indent(out) << tstruct->get_name() << "StandardScheme.write(oproto, this)" << endl;
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_standard_scheme_read(std::ostream& out,
+ t_struct* tstruct) {
+ indent(out) << "override fun read(iproto: org.apache.thrift.protocol.TProtocol, struct: "
+ << tstruct->get_name() << ") {" << endl;
+ indent_up();
+ {
+ indent(out) << "iproto.apply {" << endl;
+ indent_up();
+ {
+ indent(out) << "readStruct {" << endl;
+ indent_up();
+ {
+ indent(out) << "var stopped = false" << endl;
+ indent(out) << "while (!stopped) {" << endl;
+ indent_up();
+ {
+ indent(out) << "stopped = readField {" << endl;
+ indent_up();
+ {
+ indent(out) << "val skipNext = { "
+ "org.apache.thrift.protocol.TProtocolUtil.skip(iproto, type) }"
+ << endl;
+
+ indent(out) << "when (id.toInt()) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ indent(out) << field->get_key() << " -> {" << endl;
+ indent_up();
+ {
+ indent(out) << "if (type == " << type_to_enum(field->get_type()) << ") {" << endl;
+ indent_up();
+ generate_deserialize_field(out, field, "struct.");
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "skipNext()" << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ }
+ scope_down(out);
+ }
+ indent(out) << "else -> skipNext()" << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ indent(out) << "struct.validate()" << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_standard_scheme_write(std::ostream& out,
+ t_struct* tstruct) {
+ indent(out) << "override fun write(oproto: org.apache.thrift.protocol.TProtocol, struct: "
+ << tstruct->get_name() << ") {" << endl;
+ indent_up();
+ {
+ indent(out) << "struct.validate()" << endl;
+ indent(out) << "oproto.apply {" << endl;
+ indent_up();
+ {
+ indent(out) << "writeStruct(STRUCT_DESC) {" << endl;
+ indent_up();
+ {
+ for (auto& field : tstruct->get_members()) {
+ auto is_required = field->get_req() == t_field::T_REQUIRED;
+ indent(out) << "struct." << kotlin_safe_name(field->get_name())
+ << (is_required ? "" : "?") << ".let {" << endl;
+ indent_up();
+ {
+ indent(out) << "writeField(" << constant_name(field->get_name()) << "_FIELD_DESC) {"
+ << endl;
+ indent_up();
+ generate_serialize_field(out, field);
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ }
+ indent(out) << "writeFieldStop()" << endl;
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_struct_standard_scheme(std::ostream& out, t_struct* tstruct) {
+ indent(out) << "private object " << tstruct->get_name()
+ << "StandardScheme : org.apache.thrift.scheme.StandardScheme<" << tstruct->get_name()
+ << ">() {" << endl;
+ indent_up();
+ generate_struct_standard_scheme_read(out, tstruct);
+ generate_struct_standard_scheme_write(out, tstruct);
+ scope_down(out);
+ out << endl;
+}
+
+void t_kotlin_generator::generate_union_tuple_scheme(std::ostream& out, t_struct* /*tunion*/) {
+ indent(out) << "override fun tupleSchemeReadValue(iproto: org.apache.thrift.protocol.TProtocol, "
+ "fieldID: kotlin.Short) = throw kotlin.UnsupportedOperationException(\"only "
+ "standard scheme is supported for now\")"
+ << endl;
+ indent(out)
+ << "override fun tupleSchemeWriteValue(oproto: org.apache.thrift.protocol.TProtocol) = "
+ "throw kotlin.UnsupportedOperationException(\"only standard scheme is supported for "
+ "now\")"
+ << endl;
+}
+
+void t_kotlin_generator::generate_union_standard_scheme(std::ostream& out, t_struct* tunion) {
+ generate_union_standard_scheme_read(out, tunion);
+ generate_union_standard_scheme_write(out, tunion);
+}
+
+void t_kotlin_generator::generate_union_standard_scheme_read(std::ostream& out, t_struct* tunion) {
+ indent(out)
+ << "override fun standardSchemeReadValue(iproto: org.apache.thrift.protocol.TProtocol, "
+ "field: org.apache.thrift.protocol.TField): Any? ="
+ << endl;
+ indent_up();
+ indent(out) << "when (_Fields.findByValue(field.id.toInt())) {" << endl;
+ indent_up();
+ for (auto& member : tunion->get_members()) {
+ auto expect_type = type_name(member->get_type());
+ indent(out) << "_Fields." << constant_name(member->get_name()) << " -> {" << endl;
+ indent_up();
+ {
+ indent(out) << "if (field.type == " << constant_name(member->get_name())
+ << "_FIELD_DESC.type) {" << endl;
+ indent_up();
+ indent(out) << "iproto.run {" << endl;
+ indent_up();
+ indent(out);
+ generate_deserialize_value(out, member->get_type());
+ out << endl;
+ scope_down(out);
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "org.apache.thrift.protocol.TProtocolUtil.skip(iproto, field.type)" << endl;
+ indent(out) << "null" << endl;
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ indent(out) << "null -> {" << endl;
+ indent_up();
+ indent(out) << "org.apache.thrift.protocol.TProtocolUtil.skip(iproto, field.type)" << endl;
+ indent(out) << "null" << endl;
+ scope_down(out);
+ scope_down(out);
+ indent_down();
+}
+
+void t_kotlin_generator::generate_union_standard_scheme_write(std::ostream& out, t_struct* tunion) {
+ indent(out) << "@Suppress(\"UNCHECKED_CAST\")" << endl;
+ indent(out)
+ << "override fun standardSchemeWriteValue(oproto: org.apache.thrift.protocol.TProtocol) {"
+ << endl;
+ indent_up();
+ indent(out) << "when (setField_) {" << endl;
+ indent_up();
+ for (auto& member : tunion->get_members()) {
+ indent(out) << "_Fields." << constant_name(member->get_name()) << " -> {" << endl;
+ indent_up();
+ {
+ indent(out) << "val it = value_ as " << type_name(member->get_type()) << endl;
+ indent(out) << "oproto.apply {" << endl;
+ indent_up();
+ {
+ indent(out);
+ generate_serialize_value(out, member->get_type());
+ out << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ indent(out) << "null -> throw kotlin.IllegalStateException(\"Cannot write union with unknown "
+ "field $setField_\")"
+ << endl;
+ scope_down(out);
+ scope_down(out);
+}
+void t_kotlin_generator::generate_union_methods_definitions(std::ostream& out, t_struct* tunion) {
+ {
+ // this is a hack to reuse code
+ t_struct union_fields(program_, tunion->get_name());
+ t_enum enum_type(program_);
+ enum_type.set_name("setField_");
+ t_field set_field(&enum_type, "setField_", 0);
+ t_base_type value_type("value_", t_base_type::TYPE_STRING);
+ value_type.set_binary(true);
+ t_field value(&value_type, "value_", 1);
+ union_fields.append(&set_field);
+ union_fields.append(&value);
+ generate_struct_method_compare_to(out, &union_fields);
+ }
+
+ auto union_class_name = kotlin_safe_name(tunion->get_name());
+ { indent(out) << "override fun deepCopy() = " << union_class_name << "(this)" << endl; }
+ { indent(out) << "override fun enumForId(id: kotlin.Short) = fieldForId(id.toInt())" << endl; }
+ { indent(out) << "override fun getStructDesc() = STRUCT_DESC" << endl; }
+ {
+ indent(out) << "override fun getFieldDesc(setField: _Fields) = when (setField) {" << endl;
+ indent_up();
+ for (auto& member : tunion->get_members()) {
+ indent(out) << "_Fields." << constant_name(member->get_name()) << " -> "
+ << constant_name(member->get_name()) << "_FIELD_DESC" << endl;
+ }
+ scope_down(out);
+ }
+}
+
+void t_kotlin_generator::generate_union_method_check_type(std::ostream& out, t_struct* tunion) {
+ indent(out) << "@Suppress(\"UNCHECKED_CAST\")" << endl;
+ indent(out) << "override fun checkType(setField: _Fields, value: kotlin.Any?) {" << endl;
+ indent_up();
+ indent(out) << "when (setField) {" << endl;
+ indent_up();
+ for (auto& member : tunion->get_members()) {
+ auto expect_type = type_name(member->get_type());
+ indent(out) << "_Fields." << constant_name(member->get_name()) << " -> value as? "
+ << expect_type
+ << " ?: throw kotlin.ClassCastException(\"Was expecting value of type `"
+ << expect_type << "' for field `" << member->get_name()
+ << "', but got ${value?.javaClass}\")" << endl;
+ }
+ scope_down(out);
+ scope_down(out);
+}
+
+void t_kotlin_generator::generate_union_definition(std::ostream& out,
+ t_struct* tunion,
+ string /*additional interface*/) {
+ auto union_class_name = kotlin_safe_name(tunion->get_name());
+ indent(out) << "class " << union_class_name << " : org.apache.thrift.TUnion<" << union_class_name
+ << ", " << union_class_name << "._Fields> {" << endl;
+ indent_up();
+ indent(out) << "constructor(setField: _Fields, value: kotlin.Any) : super(setField, value)"
+ << endl;
+ indent(out) << "constructor(other: " << union_class_name << ") : super(other)" << endl;
+ indent(out) << "constructor() : super()" << endl;
+
+ generate_struct_field_name_constants(out, tunion);
+ generate_struct_companion_object(out, tunion);
+ generate_struct_method_field_for_id(out, tunion);
+ generate_union_methods_definitions(out, tunion);
+ generate_union_method_check_type(out, tunion);
+ generate_union_standard_scheme(out, tunion);
+ generate_union_tuple_scheme(out, tunion);
+ indent_down();
+ indent(out) << "}" << endl;
+}
+
+void t_kotlin_generator::generate_struct_definition(std::ostream& out,
+ t_struct* tstruct,
+ bool is_exception,
+ string additional_interface) {
+ generate_kdoc_comment(out, tstruct);
+ auto members = tstruct->get_members();
+ if (members.size() > 0) {
+ indent(out) << "data class ";
+ } else {
+ indent(out) << "class ";
+ }
+ out << kotlin_safe_name(tstruct->get_name()) << "(";
+
+ indent_up();
+ auto sep = "";
+ for (auto field : members) {
+ out << sep << endl;
+ sep = ",";
+ generate_kdoc_comment(out, field);
+ auto is_required = field->get_req() == t_field::T_REQUIRED;
+ if (is_required) {
+ indent(out) << "private var _" << field->get_name();
+ } else if (is_exception && field->get_name() == "message") {
+ // special handling for exception when field name is message - needs override
+ if (!field->get_type()->is_string()) {
+ throw "type error: for `message' field in an exception struct, it must be a string";
+ }
+ indent(out) << "override var message";
+ } else {
+ indent(out) << "var " << kotlin_safe_name(field->get_name());
+ }
+ out << ": " << type_name(field->get_type()) << "? = null";
+ }
+ indent_down();
+ out << endl;
+ indent(out) << ") : ";
+ if (is_exception) {
+ out << "org.apache.thrift.TException(), ";
+ }
+ if (additional_interface != "") {
+ additional_interface = ", " + additional_interface;
+ }
+ out << "org.apache.thrift.TBase<" << tstruct->get_name() << ", " << tstruct->get_name()
+ << "._Fields>" << additional_interface << " {" << endl;
+
+ indent_up();
+
+ for (auto field : members) {
+ if (field->get_req() == t_field::T_REQUIRED) {
+ indent(out);
+ // special handling for exception when field name is message - needs override
+ if (is_exception && field->get_name() == "message") {
+ out << "override ";
+ }
+ out << "val " << kotlin_safe_name(field->get_name()) << ": " << type_name(field->get_type())
+ << " get() = _" + kotlin_safe_name(field->get_name()) << "!!" << endl;
+ }
+ }
+
+ generate_struct_field_name_constants(out, tstruct);
+ generate_struct_companion_object(out, tstruct);
+ generate_struct_standard_scheme(out, tstruct);
+ generate_struct_method_compare_to(out, tstruct);
+ generate_struct_method_field_for_id(out, tstruct);
+ generate_struct_method_get_field_value(out, tstruct);
+ generate_struct_method_set_field_value(out, tstruct);
+ generate_struct_method_is_set(out, tstruct);
+ generate_struct_method_deep_copy(out, tstruct);
+ generate_struct_method_clear(out, tstruct);
+ generate_struct_method_validate(out, tstruct);
+ generate_struct_method_read(out, tstruct);
+ generate_struct_method_write(out, tstruct);
+
+ indent_down();
+ indent(out) << "}" << endl;
+}
+
+string t_kotlin_generator::base_type_write_expression(t_base_type* tbase, string it) {
+ switch (tbase->get_base()) {
+ case t_base_type::TYPE_VOID:
+ throw "compiler error: no void in base types";
+ case t_base_type::TYPE_STRING:
+ if (tbase->is_binary()) {
+ return "writeBinary(java.nio.ByteBuffer.wrap(" + it + "))";
+ } else {
+ return "writeString(" + it + ")";
+ }
+ case t_base_type::TYPE_BOOL:
+ return "writeBool(" + it + ")";
+ case t_base_type::TYPE_I8:
+ return "writeByte(" + it + ")";
+ case t_base_type::TYPE_I16:
+ return "writeI16(" + it + ")";
+ case t_base_type::TYPE_I32:
+ return "writeI32(" + it + ")";
+ case t_base_type::TYPE_I64:
+ return "writeI64(" + it + ")";
+ case t_base_type::TYPE_DOUBLE:
+ return "writeDouble(" + it + ")";
+ default:
+ throw "compiler error: no Kotlin name for base type "
+ + t_base_type::t_base_name(tbase->get_base());
+ }
+}
+
+string t_kotlin_generator::base_type_read_expression(t_base_type* tbase) {
+ switch (tbase->get_base()) {
+ case t_base_type::TYPE_VOID:
+ throw "compiler error: no void in base types";
+ case t_base_type::TYPE_STRING:
+ if (tbase->is_binary()) {
+ return "org.apache.thrift.TBaseHelper.byteBufferToByteArray(readBinary())";
+ } else {
+ return "readString()";
+ }
+ case t_base_type::TYPE_BOOL:
+ return "readBool()";
+ case t_base_type::TYPE_I8:
+ return "readByte()";
+ case t_base_type::TYPE_I16:
+ return "readI16()";
+ case t_base_type::TYPE_I32:
+ return "readI32()";
+ case t_base_type::TYPE_I64:
+ return "readI64()";
+ case t_base_type::TYPE_DOUBLE:
+ return "readDouble()";
+ default:
+ throw "compiler error: no Kotlin name for base type "
+ + t_base_type::t_base_name(tbase->get_base());
+ }
+}
+
+void t_kotlin_generator::generate_serialize_value(ostream& out, t_type* type, string it) {
+ t_type* ttype = get_true_type(type);
+ if (ttype->is_struct() || ttype->is_xception()) {
+ out << it << ".write(this)";
+ } else if (ttype->is_container()) {
+ generate_serialize_container(out, ttype, it);
+ } else if (ttype->is_base_type()) {
+ out << base_type_write_expression((t_base_type*)ttype, it);
+ } else if (ttype->is_enum()) {
+ out << "writeI32(" << it << ".value)";
+ } else {
+ printf("cannot deserialize type '%s'\n", type_name(ttype).c_str());
+ }
+}
+
+void t_kotlin_generator::generate_deserialize_value(ostream& out, t_type* type) {
+ t_type* ttype = get_true_type(type);
+ if (ttype->is_struct() || ttype->is_xception()) {
+ out << type_name(ttype) << "().apply { read(iproto) }";
+ } else if (ttype->is_container()) {
+ generate_deserialize_container(out, ttype);
+ } else if (ttype->is_base_type()) {
+ out << base_type_read_expression((t_base_type*)ttype);
+ } else if (ttype->is_enum()) {
+ out << "requireNotNull(" << type_name(ttype, false, false, true) + ".findByValue(readI32()))";
+ } else {
+ printf("cannot deserialize type '%s'\n", type_name(ttype).c_str());
+ }
+}
+
+/**
+ * Serializes a field of any type.
+ *
+ * @param tfield The field
+ */
+void t_kotlin_generator::generate_serialize_field(ostream& out, t_field* tfield) {
+ t_type* type = get_true_type(tfield->get_type());
+ if (type->is_void()) {
+ throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + tfield->get_name();
+ }
+ indent(out);
+ generate_serialize_value(out, type);
+ out << endl;
+}
+
+/**
+ * Deserializes a field of any type.
+ *
+ * @param tfield The field
+ * @param prefix The variable name or container for this field
+ */
+void t_kotlin_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) {
+ t_type* type = get_true_type(tfield->get_type());
+ if (type->is_void()) {
+ throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name();
+ }
+ auto is_required = tfield->get_req() == t_field::T_REQUIRED;
+ string name
+ = prefix + (is_required ? "_" + tfield->get_name() : kotlin_safe_name(tfield->get_name()));
+ indent(out) << name << " = ";
+ generate_deserialize_value(out, type);
+ out << endl;
+}
+
+/**
+ * Serializes a container by writing its size and then iterating
+ */
+void t_kotlin_generator::generate_serialize_container(ostream& out, t_type* ttype, string it) {
+ if (ttype->is_map()) {
+ out << "writeMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", "
+ << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << it << ") { (key, value) ->"
+ << endl;
+ indent_up();
+ {
+ generate_serialize_value(indent(out), ((t_map*)ttype)->get_key_type(), "key");
+ out << endl;
+ generate_serialize_value(indent(out), ((t_map*)ttype)->get_val_type(), "value");
+ out << endl;
+ indent_down();
+ }
+ indent(out) << "}";
+ } else if (ttype->is_set()) {
+ out << "writeSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " << it << ") {"
+ << endl;
+ indent_up();
+ {
+ generate_serialize_value(indent(out), ((t_set*)ttype)->get_elem_type());
+ out << endl;
+ indent_down();
+ }
+ indent(out) << "}";
+ } else if (ttype->is_list()) {
+ out << "writeList(" << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << it << ") {"
+ << endl;
+ {
+ indent_up();
+ generate_serialize_value(indent(out), ((t_list*)ttype)->get_elem_type());
+ out << endl;
+ indent_down();
+ }
+ indent(out) << "}";
+ } else {
+ throw "not a container type: " + ttype->get_name();
+ }
+}
+
+/**
+ * Deserializes a container by reading its size and then iterating
+ */
+void t_kotlin_generator::generate_deserialize_container(ostream& out, t_type* ttype) {
+ if (ttype->is_map()) {
+ out << "readMap {" << endl;
+ indent_up();
+ indent(out) << "kotlin.collections.List(size) {" << endl;
+ indent_up();
+ indent(out);
+ generate_deserialize_value(out, ((t_map*)ttype)->get_key_type());
+ out << " to ";
+ generate_deserialize_value(out, ((t_map*)ttype)->get_val_type());
+ out << endl;
+ indent_down();
+ indent(out) << "}.associate { it }" << endl;
+ indent_down();
+ indent(out) << "}";
+ } else if (ttype->is_set()) {
+ out << "readSet {" << endl;
+ indent_up();
+ indent(out) << "kotlin.collections.List(size) {" << endl;
+ indent_up();
+ indent(out);
+ generate_deserialize_value(out, ((t_set*)ttype)->get_elem_type());
+ out << endl;
+ indent_down();
+ indent(out) << "}.toSet()" << endl;
+ indent_down();
+ indent(out) << "}";
+ } else if (ttype->is_list()) {
+ out << "readList {" << endl;
+ indent_up();
+ indent(out) << "kotlin.collections.List(size) {" << endl;
+ indent_up();
+ indent(out);
+ generate_deserialize_value(out, ((t_list*)ttype)->get_elem_type());
+ out << endl;
+ indent_down();
+ indent(out) << "}" << endl;
+ indent_down();
+ indent(out) << "}";
+ } else {
+ throw "not a container type: " + ttype->get_name();
+ }
+}
+
+string t_kotlin_generator::function_signature(t_function* tfunction, string prefix) {
+ auto result = "suspend fun " + prefix + tfunction->get_name() + "(";
+ auto arguments = tfunction->get_arglist();
+ bool first = true;
+ for (t_field* tfield : arguments->get_members()) {
+ if (first) {
+ first = false;
+ } else {
+ result += ", ";
+ }
+ result += tfield->get_name() + ": " + type_name(tfield->get_type());
+ }
+ result += "): ";
+ result += type_name(tfunction->get_returntype());
+ return result;
+}
+
+void t_kotlin_generator::generate_service_interface(t_service* tservice) {
+ string f_service_name = package_dir_ + "/" + tservice->get_name() + ".kt";
+ ofstream_with_content_based_conditional_update out;
+ out.open(f_service_name.c_str());
+ out << autogen_comment() << kotlin_package();
+ out << "interface " << tservice->get_name() << " {" << endl;
+ indent_up();
+ for (auto tfunc : tservice->get_functions()) {
+ generate_kdoc_comment(out, tfunc);
+ indent(out) << function_signature(tfunc) << endl;
+ }
+ scope_down(out);
+ out << endl << endl;
+ out.close();
+}
+
+void t_kotlin_generator::generate_service_client(t_service* tservice) {
+ string f_service_name = package_dir_ + "/" + tservice->get_name() + "Client.kt";
+ ofstream_with_content_based_conditional_update out;
+ out.open(f_service_name.c_str());
+ out << autogen_comment() << warning_surpressions() << kotlin_package();
+ generate_docstring_comment(out, "/**\n", " * ",
+ "client implementation for [" + tservice->get_name() + "]", " */\n");
+ indent(out) << "class " << tservice->get_name() << "Client(" << endl;
+ indent_up();
+ indent(out) << "protocolFactory: org.apache.thrift.protocol.TProtocolFactory," << endl;
+ indent(out) << "clientManager: org.apache.thrift.async.TAsyncClientManager," << endl;
+ indent(out) << "transport: org.apache.thrift.transport.TNonblockingTransport" << endl;
+ indent_down();
+ out << "): org.apache.thrift.async.TAsyncClient(protocolFactory, clientManager, transport), "
+ << tservice->get_name() << " {" << endl
+ << endl;
+
+ indent_up();
+ {
+ indent(out) << "private val seqId = java.util.concurrent.atomic.AtomicInteger()" << endl
+ << endl;
+ for (auto tfunc : tservice->get_functions()) {
+ indent(out) << "override " << function_signature(tfunc) << " {" << endl;
+ indent_up();
+ {
+ string args_name = tservice->get_name() + "FunctionArgs." + tfunc->get_name() + "_args";
+ indent(out) << "val args = " << args_name << "(";
+ auto first = true;
+ for (auto tfield : tfunc->get_arglist()->get_members()) {
+ if (!first) {
+ out << ", ";
+ }
+ first = false;
+ out << tfield->get_name();
+ }
+ out << ")" << endl;
+ indent(out) << "return transformCallback {" << endl;
+ indent_up();
+ {
+ indent(out) << "checkReady()" << endl;
+ indent(out)
+ << "___currentMethod = ProcessCall." << tfunc->get_name()
+ << "Call(args, seqId.getAndIncrement(), this, ___protocolFactory, ___transport, it)"
+ << endl;
+ indent(out) << "___manager.call(___currentMethod)" << endl;
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+
+ indent(out) << "private suspend fun <R> "
+ "org.apache.thrift.async.TAsyncClient.transformCallback(action: "
+ "(org.apache.thrift.async.AsyncMethodCallback<R>) -> Unit): R {"
+ << endl;
+ indent_up();
+ indent(out) << "val deferred = kotlinx.coroutines.CompletableDeferred<R>()" << endl;
+ indent(out) << "val callback = object : org.apache.thrift.async.AsyncMethodCallback<R> {"
+ << endl;
+ indent_up();
+ indent(out) << "override fun onComplete(response: R) { deferred.complete(response) }" << endl;
+ indent(out) << "override fun onError(exception: java.lang.Exception) { "
+ "deferred.completeExceptionally(exception) }"
+ << endl;
+ scope_down(out);
+ indent(out) << "action(callback)" << endl;
+ indent(out) << "return deferred.await()" << endl;
+ scope_down(out);
+
+ indent(out) << "sealed interface ProcessCall {" << endl;
+ indent_up();
+ for (auto tfunc : tservice->get_functions()) {
+ generate_client_call(out, tservice, tfunc);
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ out << endl << endl;
+ out.close();
+}
+
+void t_kotlin_generator::generate_client_call(std::ostream& out,
+ t_service* tservice,
+ t_function* tfunc) {
+ string funname = tfunc->get_name();
+ string funclassname = funname + "Call";
+ string rtype = type_name(tfunc->get_returntype(), true);
+
+ indent(out) << "class " + funclassname + "(" << endl;
+ indent_up();
+ string args_name = tservice->get_name() + "FunctionArgs." + tfunc->get_name() + "_args";
+ indent(out) << "val args: " << args_name << "," << endl;
+ indent(out) << "val seqId: kotlin.Int," << endl;
+ indent(out) << "client: org.apache.thrift.async.TAsyncClient," << endl;
+ indent(out) << "protocolFactory: org.apache.thrift.protocol.TProtocolFactory," << endl;
+ indent(out) << "transport: org.apache.thrift.transport.TNonblockingTransport," << endl;
+ indent(out) << "resultHandler: org.apache.thrift.async.AsyncMethodCallback<" << rtype << ">,"
+ << endl;
+ indent_down();
+ indent(out) << ") : org.apache.thrift.async.TAsyncMethodCall<" << rtype
+ << ">(client, protocolFactory, transport, resultHandler, "
+ << (tfunc->is_oneway() ? "true" : "false") << "), ProcessCall {" << endl;
+
+ indent_up();
+ indent(out) << "override fun write_args(protocol: org.apache.thrift.protocol.TProtocol) {"
+ << endl;
+ indent_up();
+ indent(out) << "val marker = org.apache.thrift.protocol.TMessage(\"" << tfunc->get_name()
+ << "\", org.apache.thrift.protocol.TMessageType.CALL, seqId)" << endl;
+ indent(out) << "protocol.writeMessage(marker) { args.write(protocol) }" << endl;
+ scope_down(out);
+
+ indent(out) << "override fun getResult(): " << rtype << " {" << endl;
+ indent_up();
+ indent(out) << "check(state == org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) { "
+ "\"Method call not finished!\" }"
+ << endl;
+ indent(out) << "val memoryTransport = "
+ "org.apache.thrift.transport.TMemoryInputTransport(frameBuffer.array())"
+ << endl;
+ indent(out) << "val protocol = client.protocolFactory.getProtocol(memoryTransport)" << endl;
+
+ if (tfunc->is_oneway()) {
+ indent(out) << "// one way function, nothing to read" << endl;
+ } else {
+ indent(out) << "return protocol.readMessage {" << endl;
+ indent_up();
+ {
+ indent(out) << "if (type == org.apache.thrift.protocol.TMessageType.EXCEPTION) {" << endl;
+ indent_up();
+ indent(out) << "val ex = org.apache.thrift.TApplicationException().apply { read(protocol) }"
+ << endl;
+ indent(out) << "throw ex" << endl;
+ scope_down(out);
+ indent(out) << "if (seqid != seqId) {" << endl;
+ indent_up();
+ indent(out) << "throw org.apache.thrift.TApplicationException(" << endl;
+ indent_up();
+ indent(out) << "org.apache.thrift.TApplicationException.BAD_SEQUENCE_ID," << endl;
+ indent(out) << "\"" << funname
+ << " failed: out of sequence response: expected $seqId but got ${seqid}\""
+ << endl;
+ indent_down();
+ indent(out) << ")" << endl;
+ scope_down(out);
+ string result_name = tservice->get_name() + "FunctionResult." + tfunc->get_name() + "_result";
+ indent(out) << "val result = " << result_name << "().apply { read(protocol) }" << endl;
+ for (auto xception : tfunc->get_xceptions()->get_members()) {
+ indent(out) << "result." << xception->get_name() << "?.let { throw it }" << endl;
+ }
+ if (!tfunc->get_returntype()->is_void()) {
+ indent(out)
+ << "result.success ?: throw "
+ "org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException."
+ "MISSING_RESULT, \"returnString failed: unknown result\")"
+ << endl;
+ }
+ }
+ scope_down(out);
+ }
+ scope_down(out);
+ scope_down(out);
+}
+
+void t_kotlin_generator::generate_service_processor(t_service* tservice) {
+ string f_service_name = package_dir_ + "/" + tservice->get_name() + "Processor.kt";
+ ofstream_with_content_based_conditional_update out;
+ out.open(f_service_name.c_str());
+ out << autogen_comment() << warning_surpressions() << kotlin_package();
+ auto service_imports = {"import kotlinx.coroutines.future.future"};
+ for (auto service_import : service_imports) {
+ out << service_import << endl;
+ }
+ out << endl;
+
+ generate_docstring_comment(out, "/**\n", " * ",
+ "server implementation for [" + tservice->get_name() + "]", " */\n");
+ indent(out) << "class " << tservice->get_name() << "Processor(" << endl;
+ indent_up();
+ indent(out) << "handler: " << tservice->get_name() << "," << endl;
+ indent(out) << "private val scope: kotlinx.coroutines.CoroutineScope," << endl;
+ indent(out) << "private val processMap: kotlin.collections.Map<kotlin.String, "
+ "org.apache.thrift.AsyncProcessFunction<"
+ << tservice->get_name()
+ << ", out org.apache.thrift.TBase<*, "
+ "*>, out kotlin.Any>> = mapOf("
+ << endl;
+ indent_up();
+ {
+ for (auto tfunc : tservice->get_functions()) {
+ indent(out) << '"' << tfunc->get_name() << '"' << " to ProcessFunction." << tfunc->get_name()
+ << "(scope)," << endl;
+ }
+ }
+ indent_down();
+ indent(out) << ")" << endl;
+ indent_down();
+ out << "): org.apache.thrift.TBaseAsyncProcessor<" << tservice->get_name()
+ << ">(handler, processMap) {" << endl;
+ indent_up();
+ indent(out) << "companion object {" << endl;
+ indent_up();
+ indent(out) << "internal val logger: org.slf4j.Logger = "
+ "org.slf4j.LoggerFactory.getLogger("
+ << tservice->get_name() << "Processor::class.java)" << endl;
+ scope_down(out);
+
+ indent(out) << "sealed interface ProcessFunction {" << endl;
+ indent_up();
+
+ {
+ for (auto tfunc : tservice->get_functions()) {
+ generate_service_process_function(out, tservice, tfunc);
+ }
+ }
+ scope_down(out);
+ scope_down(out);
+ out << endl << endl;
+ out.close();
+}
+
+void t_kotlin_generator::generate_service_process_function(ostream& out,
+ t_service* tservice,
+ t_function* tfunc) {
+ string args_name = tservice->get_name() + "FunctionArgs." + tfunc->get_name() + "_args";
+ string rtype = type_name(tfunc->get_returntype(), true);
+
+ indent(out) << "class " << tfunc->get_name() << "<I : " << tservice->get_name()
+ << ">(private val scope: kotlinx.coroutines.CoroutineScope) : "
+ "org.apache.thrift.AsyncProcessFunction<I, "
+ << args_name << ", " << rtype << ">(\"" << tfunc->get_name()
+ << "\"), ProcessFunction {" << endl;
+ indent_up();
+ {
+ indent(out) << "override fun isOneway() = " << (tfunc->is_oneway() ? "true" : "false") << endl;
+ indent(out) << "override fun getEmptyArgsInstance() = " << args_name << "()" << endl;
+ indent(out) << "override fun start(iface: I, args: " << args_name
+ << ", resultHandler: org.apache.thrift.async.AsyncMethodCallback<" << rtype
+ << ">) {" << endl;
+ indent_up();
+ indent(out) << "scope.future {" << endl;
+ indent_up();
+ indent(out) << "iface." << tfunc->get_name() << "(";
+ {
+ auto arguments = tfunc->get_arglist();
+ bool first = true;
+ for (t_field* tfield : arguments->get_members()) {
+ if (first) {
+ first = false;
+ } else {
+ out << ", ";
+ }
+ out << "args." << tfield->get_name() << "!!";
+ }
+ }
+ out << ")" << endl;
+ indent_down();
+ indent(out) << "}.whenComplete { r, t ->" << endl;
+ {
+ indent_up();
+ indent(out) << "if (t != null) {" << endl;
+ indent_up();
+ indent(out) << "resultHandler.onError(t as java.lang.Exception)" << endl;
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "resultHandler.onComplete(r)" << endl;
+ }
+ scope_down(out);
+ scope_down(out);
+ scope_down(out);
+
+ indent(out) << "override fun getResultHandler(fb: "
+ "org.apache.thrift.server.AbstractNonblockingServer.AsyncFrameBuffer, seqid: "
+ "Int) ="
+ << endl;
+ indent_up();
+ {
+ indent(out) << "object : org.apache.thrift.async.AsyncMethodCallback<" << rtype << ">{"
+ << endl;
+ indent_up();
+ {
+ indent(out) << "override fun onComplete(response: " << rtype << ") {" << endl;
+ indent_up();
+ if (tfunc->is_oneway()) {
+ indent(out) << "// one way function, no result handling" << endl;
+ } else {
+ string result_name
+ = tservice->get_name() + "FunctionResult." + tfunc->get_name() + "_result";
+ indent(out) << "val result = " << result_name << "()" << endl;
+ if (!tfunc->get_returntype()->is_void()) {
+ indent(out) << "result.success = response" << endl;
+ }
+ indent(out) << "try {" << endl;
+ indent_up();
+ indent(out)
+ << "sendResponse(fb, result, org.apache.thrift.protocol.TMessageType.REPLY, seqid)"
+ << endl;
+ indent_down();
+ indent(out) << "} catch (e: org.apache.thrift.transport.TTransportException) {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"TTransportException writing to internal frame buffer\", e)"
+ << endl;
+ indent(out) << "fb.close()" << endl;
+ indent_down();
+ indent(out) << "} catch (e: Exception) {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"Exception writing to internal frame buffer\", e)" << endl;
+ indent(out) << "onError(e)" << endl;
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ {
+ indent(out) << "override fun onError(exception: kotlin.Exception) {" << endl;
+ indent_up();
+ if (tfunc->is_oneway()) {
+ indent(out) << "if (exception is org.apache.thrift.transport.TTransportException) {"
+ << endl;
+ indent_up();
+ indent(out) << "logger.error(\"TTransportException inside handler\", exception)" << endl;
+ indent(out) << "fb.close()" << endl;
+ indent_down();
+ indent(out) << "} else {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"Exception inside oneway handler\", exception)" << endl;
+ scope_down(out);
+ } else {
+ indent(out) << "val (msgType, msg) = when (exception) {" << endl;
+ indent_up();
+
+ auto xceptions = tfunc->get_xceptions()->get_members();
+ for (auto xception : xceptions) {
+ indent(out) << "is " << type_name(xception->get_type()) << " -> {" << endl;
+ indent_up();
+ string result_name
+ = tservice->get_name() + "FunctionResult." + tfunc->get_name() + "_result";
+ indent(out) << "val result = " << result_name << "()" << endl;
+ indent(out) << "result." << xception->get_name() << " = exception" << endl;
+ indent(out) << "org.apache.thrift.protocol.TMessageType.REPLY to result" << endl;
+ scope_down(out);
+ }
+
+ indent(out) << "is org.apache.thrift.transport.TTransportException -> {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"TTransportException inside handler\", exception)" << endl;
+ indent(out) << "fb.close()" << endl;
+ indent(out) << "return" << endl;
+ scope_down(out);
+
+ indent(out) << "is org.apache.thrift.TApplicationException -> {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"TApplicationException inside handler\", exception)"
+ << endl;
+ indent(out) << "org.apache.thrift.protocol.TMessageType.EXCEPTION to exception" << endl;
+ scope_down(out);
+
+ indent(out) << "else -> {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"Exception inside handler\", exception)" << endl;
+ indent(out) << "org.apache.thrift.protocol.TMessageType.EXCEPTION to "
+ "org.apache.thrift.TApplicationException(org.apache.thrift."
+ "TApplicationException.INTERNAL_ERROR, exception.message)"
+ << endl;
+ scope_down(out);
+ scope_down(out);
+
+ indent(out) << "try {" << endl;
+ indent_up();
+ indent(out) << "sendResponse(fb, msg, msgType, seqid)" << endl;
+ indent_down();
+ indent(out) << "} catch (ex: java.lang.Exception) {" << endl;
+ indent_up();
+ indent(out) << "logger.error(\"Exception writing to internal frame buffer\", ex)" << endl;
+ indent(out) << "fb.close()" << endl;
+ scope_down(out);
+ }
+
+ scope_down(out);
+ }
+ scope_down(out);
+ }
+ indent_down();
+ }
+ scope_down(out);
+}
+
+void t_kotlin_generator::generate_service_result_helpers(t_service* tservice) {
+ string f_service_result_name = package_dir_ + "/" + tservice->get_name() + "FunctionResult.kt";
+ ofstream_with_content_based_conditional_update out;
+ out.open(f_service_result_name.c_str());
+ out << autogen_comment() << warning_surpressions() << kotlin_package();
+
+ generate_docstring_comment(out, "/**\n", " * ",
+ "function result for [" + tservice->get_name() + "]", " */\n");
+ indent(out) << "sealed interface " << tservice->get_name() << "FunctionResult {" << endl;
+ indent_up();
+ for (auto func : tservice->get_functions()) {
+ if (func->is_oneway()) {
+ continue;
+ }
+ t_struct result(program_, func->get_name() + "_result");
+ t_field success(func->get_returntype(), "success", 0);
+ if (!func->get_returntype()->is_void()) {
+ result.append(&success);
+ }
+ for (auto& member : func->get_xceptions()->get_members()) {
+ result.append(member);
+ }
+ generate_struct_definition(out, &result, false, tservice->get_name() + "FunctionResult");
+ }
+ scope_down(out);
+ out.close();
+}
+
+void t_kotlin_generator::generate_service_args_helpers(t_service* tservice) {
+ string f_service_args_name = package_dir_ + "/" + tservice->get_name() + "FunctionArgs.kt";
+ ofstream_with_content_based_conditional_update out;
+ out.open(f_service_args_name.c_str());
+ out << autogen_comment() << warning_surpressions() << kotlin_package();
+ generate_docstring_comment(out, "/**\n", " * ",
+ "function arguments for [" + tservice->get_name() + "]", " */\n");
+ indent(out) << "sealed interface " << tservice->get_name() << "FunctionArgs {" << endl;
+ indent_up();
+ for (auto func : tservice->get_functions()) {
+ t_struct* ts = func->get_arglist();
+ generate_struct_definition(out, ts, false, tservice->get_name() + "FunctionArgs");
+ out << endl;
+ }
+ scope_down(out);
+ out.close();
+}
+
+void t_kotlin_generator::generate_service(t_service* tservice) {
+ generate_service_interface(tservice);
+ generate_service_client(tservice);
+ generate_service_processor(tservice);
+ generate_service_args_helpers(tservice);
+ generate_service_result_helpers(tservice);
+}
+
+void t_kotlin_generator::generate_xception(t_struct* txception) {
+ generate_kotlin_struct(txception, true);
+}
+
+/**
+ * Converts the parse type to a Java enum string for the given type.
+ */
+string t_kotlin_generator::type_to_enum(t_type* type) {
+ type = get_true_type(type);
+
+ if (type->is_base_type()) {
+ t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+ switch (tbase) {
+ case t_base_type::TYPE_VOID:
+ throw "NO T_VOID CONSTRUCT";
+ case t_base_type::TYPE_STRING:
+ return "org.apache.thrift.protocol.TType.STRING";
+ case t_base_type::TYPE_BOOL:
+ return "org.apache.thrift.protocol.TType.BOOL";
+ case t_base_type::TYPE_I8:
+ return "org.apache.thrift.protocol.TType.BYTE";
+ case t_base_type::TYPE_I16:
+ return "org.apache.thrift.protocol.TType.I16";
+ case t_base_type::TYPE_I32:
+ return "org.apache.thrift.protocol.TType.I32";
+ case t_base_type::TYPE_I64:
+ return "org.apache.thrift.protocol.TType.I64";
+ case t_base_type::TYPE_DOUBLE:
+ return "org.apache.thrift.protocol.TType.DOUBLE";
+ }
+ } else if (type->is_enum()) {
+ return "org.apache.thrift.protocol.TType.I32";
+ } else if (type->is_struct() || type->is_xception()) {
+ return "org.apache.thrift.protocol.TType.STRUCT";
+ } else if (type->is_map()) {
+ return "org.apache.thrift.protocol.TType.MAP";
+ } else if (type->is_set()) {
+ return "org.apache.thrift.protocol.TType.SET";
+ } else if (type->is_list()) {
+ return "org.apache.thrift.protocol.TType.LIST";
+ }
+
+ throw "INVALID TYPE IN type_to_enum: " + type->get_name();
+}
+
+string t_kotlin_generator::inner_enum_type_name(t_type* ttype) {
+ ttype = get_true_type(ttype);
+ if (ttype->is_map()) {
+ t_map* tmap = (t_map*)ttype;
+ t_type* key_type = get_true_type(tmap->get_key_type());
+ return type_name(key_type, true) + ".class";
+ } else if (ttype->is_set()) {
+ t_set* tset = (t_set*)ttype;
+ t_type* elem_type = get_true_type(tset->get_elem_type());
+ return type_name(elem_type, true) + ".class";
+ }
+ return "";
+}
+
+bool t_kotlin_generator::is_enum_set(t_type* ttype) {
+ ttype = get_true_type(ttype);
+ if (ttype->is_set()) {
+ t_set* tset = (t_set*)ttype;
+ t_type* elem_type = get_true_type(tset->get_elem_type());
+ return elem_type->is_enum();
+ }
+ return false;
+}
+
+bool t_kotlin_generator::is_enum_map(t_type* ttype) {
+ ttype = get_true_type(ttype);
+ if (ttype->is_map()) {
+ t_map* tmap = (t_map*)ttype;
+ t_type* key_type = get_true_type(tmap->get_key_type());
+ return key_type->is_enum();
+ }
+ return false;
+}
+
+/**
+ * Packages the generated file
+ *
+ * @return String of the package, i.e. "package org.apache.thriftdemo"
+ */
+string t_kotlin_generator::kotlin_package() {
+ if (!package_name_.empty()) {
+ return string("package ") + package_name_ + endl + endl;
+ }
+ return "";
+}
+
+string t_kotlin_generator::warning_surpressions() {
+ return "@file:Suppress(\"ClassName\", \"PropertyName\", \"RedundantUnitReturnType\", "
+ "\"NestedLambdaShadowedImplicitParameter\", "
+ "\"RemoveRedundantQualifierName\")"
+ + endl;
+}
+
+string t_kotlin_generator::constant_name(string name) {
+ string constant_name;
+ bool is_first = true;
+ bool was_previous_char_upper = false;
+ for (char character : name) {
+ bool is_upper = isupper(character);
+ if (is_upper && !is_first && !was_previous_char_upper) {
+ constant_name += '_';
+ }
+ constant_name += toupper(character);
+ is_first = false;
+ was_previous_char_upper = is_upper;
+ }
+ return constant_name;
+}
+
+bool t_kotlin_generator::is_reserved(const string& name) {
+ return KOTLIN_RESERVED_WORDS_SET.find(name) != KOTLIN_RESERVED_WORDS_SET.end();
+}
+
+string t_kotlin_generator::kotlin_safe_name(const string& name) {
+ if (is_reserved(name)) {
+ return "`" + name + "`";
+ } else {
+ return name;
+ }
+}
+
+void t_kotlin_generator::generate_kdoc_comment(ostream& out, t_doc* tdoc) {
+ if (tdoc->has_doc()) {
+ generate_docstring_comment(out, "/**\n", " * ", tdoc->get_doc(), " */\n");
+ }
+}
+
+THRIFT_REGISTER_GENERATOR(kotlin, "Kotlin", "")
diff --git a/configure.ac b/configure.ac
index 4656fbb10..197d3ae32 100755
--- a/configure.ac
+++ b/configure.ac
@@ -113,6 +113,7 @@ if test "$enable_libs" = "no"; then
with_cpp="no"
with_c_glib="no"
with_java="no"
+ with_kotlin="no"
with_python="no"
with_py3="no"
with_ruby="no"
@@ -206,6 +207,17 @@ if test "$with_java" = "yes"; then
fi
AM_CONDITIONAL([WITH_JAVA], [test "$have_java" = "yes"])
+AX_THRIFT_LIB(kotlin, [Kotlin], yes)
+if test "$with_kotlin" = "yes"; then
+ AX_JAVAC_AND_JAVA
+ AC_SUBST(CLASSPATH)
+ AC_SUBST(GRADLE_OPTS)
+ if test "x$JAVA" != "x" && test "x$JAVAC" != "x" ; then
+ have_kotlin="yes"
+ fi
+fi
+AM_CONDITIONAL([WITH_KOTLIN], [test "$have_kotlin" = "yes"])
+
AX_THRIFT_LIB(erlang, [Erlang], yes)
if test "$with_erlang" = "yes"; then
AC_ERLANG_PATH_ERL
@@ -743,6 +755,7 @@ AC_CONFIG_FILES([
lib/js/test/Makefile
lib/json/Makefile
lib/json/test/Makefile
+ lib/kotlin/Makefile
lib/netstd/Makefile
lib/nodejs/Makefile
lib/nodets/Makefile
@@ -811,6 +824,8 @@ if test "$have_d" = "yes" -a "$have_deimos_event2" = "yes" -a "$have_deimos_open
AC_SUBST([MAYBE_D])
if test "$have_java" = "yes" ; then MAYBE_JAVA="java" ; else MAYBE_JAVA="" ; fi
AC_SUBST([MAYBE_JAVA])
+if test "$have_kotlin" = "yes" ; then MAYBE_KOTLIN="kotlin" ; else MAYBE_KOTLIN="" ; fi
+AC_SUBST([MAYBE_KOTLIN])
if test "$have_python" = "yes" ; then MAYBE_PYTHON="py" ; else MAYBE_PYTHON="" ; fi
AC_SUBST([MAYBE_PYTHON])
if test "$have_py3" = "yes" ; then MAYBE_PY3="py3" ; else MAYBE_PY3="" ; fi
@@ -855,6 +870,7 @@ echo "Building Erlang Library ...... : $have_erlang"
echo "Building Go Library .......... : $have_go"
echo "Building Haxe Library ........ : $have_haxe"
echo "Building Java Library ........ : $have_java"
+echo "Building Kotlin Library ...... : $have_kotlin"
echo "Building Lua Library ......... : $have_lua"
echo "Building NodeJS Library ...... : $have_nodejs"
echo "Building Perl Library ........ : $have_perl"
@@ -928,6 +944,15 @@ if test "$have_java" = "yes" ; then
echo " Using Gradle version ...... : $($GRADLE --version --quiet | grep Gradle 2>&1)"
echo " Using java version ........ : $($JAVA -version 2>&1 | grep 'version ')"
fi
+if test "$have_kotlin" = "yes" ; then
+ echo
+ echo "Kotlin (Test Only) Library:"
+ echo " Using gradlew ............. : lib/kotlin/gradlew"
+ echo " Using java ................ : $JAVA"
+ echo " Using javac ............... : $JAVAC"
+ echo " Using Gradle version ...... : $(lib/kotlin/gradlew --version --quiet | grep Gradle 2>&1)"
+ echo " Using java version ........ : $($JAVA -version 2>&1 | grep 'version ')"
+fi
if test "$have_lua" = "yes" ; then
echo
echo "Lua Library:"
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 141122124..64b903094 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -36,6 +36,11 @@ PRECROSS_TARGET += precross-java
SUBDIRS += js
endif
+if WITH_KOTLIN
+SUBDIRS += kotlin
+PRECROSS_TARGET += precross-kotlin
+endif
+
if WITH_PYTHON
SUBDIRS += py
endif
diff --git a/lib/kotlin/.gitignore b/lib/kotlin/.gitignore
new file mode 100644
index 000000000..7d5c78976
--- /dev/null
+++ b/lib/kotlin/.gitignore
@@ -0,0 +1,78 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Created by https://www.toptal.com/developers/gitignore/api/gradle,kotlin
+# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,kotlin
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+# !gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+# !gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin
+out/
+
+gradlew
+gradlew.bat
+gradle/wrapper/gradle-wrapper.jar
+gradle/wrapper/gradle-wrapper.properties
diff --git a/lib/kotlin/CMakeLists.txt b/lib/kotlin/CMakeLists.txt
new file mode 100644
index 000000000..d81287c00
--- /dev/null
+++ b/lib/kotlin/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+file(GLOB_RECURSE THRIFTKOTLIN_SOURCES LIST_DIRECTORIES false
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/*")
diff --git a/lib/kotlin/Makefile.am b/lib/kotlin/Makefile.am
new file mode 100644
index 000000000..6a99628a2
--- /dev/null
+++ b/lib/kotlin/Makefile.am
@@ -0,0 +1,44 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+export CLASSPATH
+
+all-local:
+ $(GRADLE) $(GRADLE_OPTS) assemble \
+ -Pthrift.version=$(PACKAGE_VERSION) \
+ -Pthrift.compiler=$(THRIFT) \
+ --console=plain
+
+clean-local:
+ $(GRADLE) $(GRADLE_OPTS) clean --console=plain
+
+precross: $(THRIFT)
+ $(GRADLE) $(GRADLE_OPTS) installDist \
+ -Pthrift.version=$(PACKAGE_VERSION) \
+ -Pthrift.compiler=$(THRIFT) \
+ --console=plain
+
+check-local: $(THRIFT)
+ $(GRADLE) $(GRADLE_OPTS) test \
+ -Pthrift.version=$(PACKAGE_VERSION) \
+ -Pthrift.compiler=$(THRIFT) \
+ --console=plain
+
+EXTRA_DIST = \
+ README.md
diff --git a/lib/kotlin/README.md b/lib/kotlin/README.md
new file mode 100644
index 000000000..2f73dea14
--- /dev/null
+++ b/lib/kotlin/README.md
@@ -0,0 +1,29 @@
+# Test Only Library for Kotlin
+
+This directory (`/lib/kotlin`) contains test only library code for Kotlin code gen. Because Kotlin code gen produces code that works on top of libthrift (i.e. Java library), the purpose of this library is to encode the cross test server and client to make sure it conforms to the thrift specifications.
+
+The output artifact in this library is *not* published to Maven central.
+
+## How to compile
+
+This library is managed using Gradle 7+, so the easiest way is to use gradle wrapper (`./gradlew`).
+
+Run the following command (requires C++ compiler):
+
+```bash
+gradle build
+```
+
+```bash
+gradle installDist
+```
+
+## How to run cross test server / client
+
+```bash
+gradle :cross-test-server:run
+```
+
+```bash
+gradle :cross-test-client:run
+```
diff --git a/lib/kotlin/cross-test-client/build.gradle.kts b/lib/kotlin/cross-test-client/build.gradle.kts
new file mode 100644
index 000000000..3e67abfe5
--- /dev/null
+++ b/lib/kotlin/cross-test-client/build.gradle.kts
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ kotlin("jvm") version "1.5.31"
+ id("com.ncorti.ktfmt.gradle") version "0.4.0"
+ java
+ application
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+ // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.1")
+ // https://mvnrepository.com/artifact/org.apache.thrift/libthrift
+ implementation("org.apache.thrift:libthrift:INCLUDED")
+ // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
+ implementation("ch.qos.logback:logback-classic:1.3.0-alpha14")
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
+}
+
+tasks {
+ application {
+ applicationName = "TestClient"
+ mainClass.set("org.apache.thrift.test.TestClientKt")
+ }
+
+ ktfmt {
+ kotlinLangStyle()
+ }
+
+ task<Exec>("compileThrift") {
+ val thriftBin = if (hasProperty("thrift.compiler")) {
+ file(property("thrift.compiler"))
+ } else {
+ project.rootDir.resolve("../../compiler/cpp/thrift")
+ }
+ val outputDir = layout.buildDirectory.dir("generated-sources")
+ doFirst {
+ mkdir(outputDir)
+ }
+ commandLine = listOf(
+ thriftBin.absolutePath,
+ "-gen",
+ "kotlin",
+ "-out",
+ outputDir.get().toString(),
+ project.rootDir.resolve("../../test/ThriftTest.thrift").absolutePath
+ )
+ group = LifecycleBasePlugin.BUILD_GROUP
+ }
+
+ compileKotlin {
+ dependsOn("compileThrift")
+ }
+}
+
+sourceSets["main"].java {
+ srcDir(layout.buildDirectory.dir("generated-sources"))
+}
diff --git a/lib/kotlin/cross-test-client/src/main/kotlin/org/apache/thrift/test/TestClient.kt b/lib/kotlin/cross-test-client/src/main/kotlin/org/apache/thrift/test/TestClient.kt
new file mode 100644
index 000000000..10ca82927
--- /dev/null
+++ b/lib/kotlin/cross-test-client/src/main/kotlin/org/apache/thrift/test/TestClient.kt
@@ -0,0 +1,1045 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.thrift.test
+
+import org.apache.http.impl.client.HttpClients
+import org.apache.thrift.TApplicationException
+import org.apache.thrift.TException
+import org.apache.thrift.TSerializer
+import org.apache.thrift.async.TAsyncClientManager
+import org.apache.thrift.protocol.TBinaryProtocol
+import org.apache.thrift.protocol.TCompactProtocol
+import org.apache.thrift.protocol.TJSONProtocol
+import org.apache.thrift.protocol.TMultiplexedProtocol
+import org.apache.thrift.protocol.TProtocol
+import org.apache.thrift.protocol.TSimpleJSONProtocol
+import org.apache.thrift.transport.THttpClient
+import org.apache.thrift.transport.TNonblockingSocket
+import org.apache.thrift.transport.TSSLTransportFactory
+import org.apache.thrift.transport.TTransport
+import org.apache.thrift.transport.TZlibTransport
+import org.apache.thrift.transport.layered.TFastFramedTransport
+import org.apache.thrift.transport.layered.TFramedTransport
+import thrift.test.Insanity
+import thrift.test.Numberz
+import thrift.test.ThriftTestClient
+import thrift.test.Xception
+import thrift.test.Xception2
+import thrift.test.Xtruct
+import thrift.test.Xtruct2
+import java.nio.ByteBuffer
+import kotlin.math.abs
+import kotlin.system.exitProcess
+
+/**
+ * Test Java client for thrift. Essentially just a copy of the C++ version, this makes a variety of
+ * requests to enable testing for both performance and correctness of the output.
+ */
+const val ERR_BASETYPES = 1
+const val ERR_STRUCTS = 2
+const val ERR_CONTAINERS = 4
+const val ERR_EXCEPTIONS = 8
+const val ERR_PROTOCOLS = 16
+const val ERR_UNKNOWN = 64
+
+suspend fun main(args: Array<String>) {
+ var host = "localhost"
+ var port = 9090
+ var numTests = 1
+ var protocolType = "binary"
+ var transportType = "buffered"
+ var ssl = false
+ var zlib = false
+ var httpClient = false
+ var socketTimeout = 1000
+ try {
+ for (i in args.indices) {
+ if (args[i].startsWith("--host")) {
+ host = args[i].split("=").toTypedArray()[1]
+ host.trim { it <= ' ' }
+ } else if (args[i].startsWith("--port")) {
+ port = Integer.valueOf(args[i].split("=").toTypedArray()[1])
+ } else if (args[i].startsWith("--n") || args[i].startsWith("--testloops")) {
+ numTests = Integer.valueOf(args[i].split("=").toTypedArray()[1])
+ } else if (args[i] == "--timeout") {
+ socketTimeout = Integer.valueOf(args[i].split("=").toTypedArray()[1])
+ } else if (args[i].startsWith("--protocol")) {
+ protocolType = args[i].split("=").toTypedArray()[1]
+ protocolType.trim { it <= ' ' }
+ } else if (args[i].startsWith("--transport")) {
+ transportType = args[i].split("=").toTypedArray()[1]
+ transportType.trim { it <= ' ' }
+ } else if (args[i] == "--ssl") {
+ ssl = true
+ } else if (args[i] == "--zlib") {
+ zlib = true
+ } else if (args[i] == "--client") {
+ httpClient = true
+ } else if (args[i] == "--help") {
+ println("Allowed options:")
+ println(" --help\t\t\tProduce help message")
+ println(" --host=arg (=$host)\tHost to connect")
+ println(" --port=arg (=$port)\tPort number to connect")
+ println(
+ " --transport=arg (=$transportType)\n\t\t\t\tTransport: buffered, framed, fastframed, http, zlib"
+ )
+ println(
+ " --protocol=arg (=$protocolType)\tProtocol: binary, compact, json, multi, multic, multij"
+ )
+ println(" --ssl\t\t\tEncrypted Transport using SSL")
+ println(" --zlib\t\t\tCompressed Transport using Zlib")
+ println(" --testloops[--n]=arg (=$numTests)\tNumber of Tests")
+ exitProcess(0)
+ }
+ }
+ } catch (x: Exception) {
+ System.err.println("Can not parse arguments! See --help")
+ exitProcess(ERR_UNKNOWN)
+ }
+ try {
+ checkProtocolType(protocolType)
+ checkTransportType(transportType)
+ if (transportType == "http" && ssl) {
+ throw Exception("SSL is not supported over http.")
+ }
+ } catch (e: Exception) {
+ System.err.println("Error: " + e.message)
+ exitProcess(ERR_UNKNOWN)
+ }
+ val transport: TTransport
+ try {
+ transport = getTTransport(transportType, host, port, httpClient, ssl, socketTimeout, zlib)
+ } catch (x: Exception) {
+ x.printStackTrace()
+ exitProcess(ERR_UNKNOWN)
+ }
+ var tProtocol = getTProtocol(protocolType, transport)
+ var tProtocol2: TProtocol? = null
+ if (protocolType.startsWith("multi")) {
+ tProtocol2 = TMultiplexedProtocol(tProtocol, "SecondService")
+ tProtocol = TMultiplexedProtocol(tProtocol, "ThriftTest")
+ }
+ println("$tProtocol, $transport")
+
+ val clientFactory = {
+ ThriftTestClient(
+ { TBinaryProtocol(it) },
+ TAsyncClientManager(),
+ TNonblockingSocket(host, port, socketTimeout)
+ )
+ }
+
+ var testClient = clientFactory()
+ val insane = Insanity()
+ var timeMin: Long = 0
+ var timeMax: Long = 0
+ var timeTot: Long = 0
+ var returnCode = 0
+ for (test in 0 until numTests) {
+ // transport.startConnect()
+ try {
+ /** CONNECT TEST */
+ println("Test #${test + 1}, connect $host:$port")
+ // if (!transport.isOpen) {
+ // try {
+ // transport.open()
+ // } catch (ttx: TTransportException) {
+ // ttx.printStackTrace()
+ // println("Connect failed: " + ttx.message)
+ // exitProcess(ERR_UNKNOWN)
+ // }
+ // }
+ val start = System.nanoTime()
+ /** VOID TEST */
+ try {
+ print("testVoid()")
+ testClient.testVoid()
+ print(" = void\n")
+ } catch (tax: TApplicationException) {
+ tax.printStackTrace()
+ returnCode = returnCode or ERR_BASETYPES
+ }
+ /** STRING TEST */
+ print("testString(\"Test\")")
+ val s: String = testClient.testString("Test")
+ print(" = \"$s\"\n")
+ if (s != "Test") {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** Multiplexed test */
+ if (protocolType.startsWith("multi")) {
+ throw UnsupportedOperationException("multi protocol is not yet supported")
+ // val secondClient: SecondServiceClient =
+ // SecondServiceClient(tProtocol2)
+ // print("secondtestString(\"Test2\")")
+ // s = secondClient.secondtestString("Test2")
+ // print(" = \"$s\"\n")
+ // if (s != "testString(\"Test2\")") {
+ // returnCode = returnCode or ERR_PROTOCOLS
+ // println("*** FAILURE ***\n")
+ // }
+ }
+ /** BYTE TEST */
+ print("testByte(1)")
+ val i8: Byte = testClient.testByte(1.toByte())
+ print(" = $i8\n")
+ if (i8.toInt() != 1) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** I32 TEST */
+ print("testI32(-1)")
+ val i32: Int = testClient.testI32(-1)
+ print(" = $i32\n")
+ if (i32 != -1) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** I64 TEST */
+ print("testI64(-34359738368)")
+ val i64: Long = testClient.testI64(-34359738368L)
+ print(" = $i64\n")
+ if (i64 != -34359738368L) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** DOUBLE TEST */
+ print("testDouble(-5.325098235)")
+ val dub: Double = testClient.testDouble(-5.325098235)
+ print(" = $dub\n")
+ if (abs(dub - -5.325098235) > 0.001) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** BINARY TEST */
+ try {
+ print("testBinary(-128...127) = ")
+ val data =
+ byteArrayOf(
+ -128,
+ -127,
+ -126,
+ -125,
+ -124,
+ -123,
+ -122,
+ -121,
+ -120,
+ -119,
+ -118,
+ -117,
+ -116,
+ -115,
+ -114,
+ -113,
+ -112,
+ -111,
+ -110,
+ -109,
+ -108,
+ -107,
+ -106,
+ -105,
+ -104,
+ -103,
+ -102,
+ -101,
+ -100,
+ -99,
+ -98,
+ -97,
+ -96,
+ -95,
+ -94,
+ -93,
+ -92,
+ -91,
+ -90,
+ -89,
+ -88,
+ -87,
+ -86,
+ -85,
+ -84,
+ -83,
+ -82,
+ -81,
+ -80,
+ -79,
+ -78,
+ -77,
+ -76,
+ -75,
+ -74,
+ -73,
+ -72,
+ -71,
+ -70,
+ -69,
+ -68,
+ -67,
+ -66,
+ -65,
+ -64,
+ -63,
+ -62,
+ -61,
+ -60,
+ -59,
+ -58,
+ -57,
+ -56,
+ -55,
+ -54,
+ -53,
+ -52,
+ -51,
+ -50,
+ -49,
+ -48,
+ -47,
+ -46,
+ -45,
+ -44,
+ -43,
+ -42,
+ -41,
+ -40,
+ -39,
+ -38,
+ -37,
+ -36,
+ -35,
+ -34,
+ -33,
+ -32,
+ -31,
+ -30,
+ -29,
+ -28,
+ -27,
+ -26,
+ -25,
+ -24,
+ -23,
+ -22,
+ -21,
+ -20,
+ -19,
+ -18,
+ -17,
+ -16,
+ -15,
+ -14,
+ -13,
+ -12,
+ -11,
+ -10,
+ -9,
+ -8,
+ -7,
+ -6,
+ -5,
+ -4,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127
+ )
+ val bin: ByteBuffer = ByteBuffer.wrap(testClient.testBinary(data))
+ bin.mark()
+ val bytes = ByteArray(bin.limit() - bin.position())
+ bin[bytes]
+ bin.reset()
+ print("{")
+ var first = true
+ for (i in bytes.indices) {
+ if (first) first = false else print(", ")
+ print(bytes[i])
+ }
+ println("}")
+ if (ByteBuffer.wrap(data) != bin) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ } catch (ex: Exception) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("\n*** FAILURE ***\n")
+ ex.printStackTrace(System.out)
+ }
+ /** STRUCT TEST */
+ print("testStruct({\"Zero\", 1, -3, -5})")
+ val out = Xtruct()
+ out.string_thing = "Zero"
+ out.byte_thing = 1.toByte()
+ out.i32_thing = -3
+ out.i64_thing = -5
+ var `in`: Xtruct = testClient.testStruct(out)
+ print(
+ """ = {"${`in`.string_thing}",${`in`.byte_thing}, ${`in`.i32_thing}, ${`in`.i64_thing}}
+"""
+ )
+ if (`in` != out) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ /** NESTED STRUCT TEST */
+ print("testNest({1, {\"Zero\", 1, -3, -5}), 5}")
+ val out2 = Xtruct2()
+ out2.byte_thing = 1.toShort().toByte()
+ out2.struct_thing = out
+ out2.i32_thing = 5
+ val in2: Xtruct2 = testClient.testNest(out2)
+ `in` = in2.struct_thing!!
+ print(
+ """ = {${in2.byte_thing}, {"${`in`.string_thing}", ${`in`.byte_thing}, ${`in`.i32_thing}, ${`in`.i64_thing}}, ${in2.i32_thing}}
+"""
+ )
+ if (in2 != out2) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ /** MAP TEST */
+ val mapout: MutableMap<Int, Int> = HashMap()
+ for (i in 0..4) {
+ mapout[i] = i - 10
+ }
+ print("testMap({")
+ var first = true
+ for (key in mapout.keys) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(key.toString() + " => " + mapout[key])
+ }
+ print("})")
+ val mapin: Map<Int, Int> = testClient.testMap(mapout)
+ print(" = {")
+ first = true
+ for (key in mapin.keys) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(key.toString() + " => " + mapout[key])
+ }
+ print("}\n")
+ if (mapout != mapin) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ }
+ /** STRING MAP TEST */
+ try {
+ val smapout: MutableMap<String, String> = HashMap()
+ smapout["a"] = "2"
+ smapout["b"] = "blah"
+ smapout["some"] = "thing"
+ for (key in smapout.keys) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(key + " => " + smapout[key])
+ }
+ print("})")
+ val smapin: Map<String, String> = testClient.testStringMap(smapout)
+ print(" = {")
+ first = true
+ for (key in smapin.keys) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(key + " => " + smapout[key])
+ }
+ print("}\n")
+ if (smapout != smapin) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ }
+ } catch (ex: Exception) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ ex.printStackTrace(System.out)
+ }
+ /** SET TEST */
+ val setout: MutableSet<Int> = HashSet()
+ for (i in -2..2) {
+ setout.add(i)
+ }
+ print("testSet({")
+ first = true
+ for (elem in setout) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(elem)
+ }
+ print("})")
+ val setin: Set<Int> = testClient.testSet(setout)
+ print(" = {")
+ first = true
+ for (elem in setin) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(elem)
+ }
+ print("}\n")
+ if (setout != setin) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ }
+ /** LIST TEST */
+ val listout: MutableList<Int> = ArrayList()
+ for (i in -2..2) {
+ listout.add(i)
+ }
+ print("testList({")
+ first = true
+ for (elem in listout) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(elem)
+ }
+ print("})")
+ val listin: List<Int> = testClient.testList(listout)
+ print(" = {")
+ first = true
+ for (elem in listin) {
+ if (first) {
+ first = false
+ } else {
+ print(", ")
+ }
+ print(elem)
+ }
+ print("}\n")
+ if (listout != listin) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ }
+ /** ENUM TEST */
+ print("testEnum(ONE)")
+ var ret: Numberz = testClient.testEnum(Numberz.ONE)
+ print(" = $ret\n")
+ if (ret !== Numberz.ONE) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ print("testEnum(TWO)")
+ ret = testClient.testEnum(Numberz.TWO)
+ print(" = $ret\n")
+ if (ret !== Numberz.TWO) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ print("testEnum(THREE)")
+ ret = testClient.testEnum(Numberz.THREE)
+ print(" = $ret\n")
+ if (ret !== Numberz.THREE) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ print("testEnum(FIVE)")
+ ret = testClient.testEnum(Numberz.FIVE)
+ print(" = $ret\n")
+ if (ret !== Numberz.FIVE) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ print("testEnum(EIGHT)")
+ ret = testClient.testEnum(Numberz.EIGHT)
+ print(" = $ret\n")
+ if (ret !== Numberz.EIGHT) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ /** TYPEDEF TEST */
+ print("testTypedef(309858235082523)")
+ val uid: Long = testClient.testTypedef(309858235082523L)
+ print(" = $uid\n")
+ if (uid != 309858235082523L) {
+ returnCode = returnCode or ERR_BASETYPES
+ println("*** FAILURE ***\n")
+ }
+ /** NESTED MAP TEST */
+ print("testMapMap(1)")
+ val mm: Map<Int, Map<Int, Int>> = testClient.testMapMap(1)
+ print(" = {")
+ for (key in mm.keys) {
+ print("$key => {")
+ val m2 = mm[key]!!
+ for (k2 in m2.keys) {
+ print(k2.toString() + " => " + m2[k2] + ", ")
+ }
+ print("}, ")
+ }
+ print("}\n")
+ if (mm.size != 2 || !mm.containsKey(4) || !mm.containsKey(-4)) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ } else {
+ val m1 = mm[4]!!
+ val m2 = mm[-4]!!
+ if (m1[1] != 1 ||
+ m1[2] != 2 ||
+ m1[3] != 3 ||
+ m1[4] != 4 ||
+ m2[-1] != -1 ||
+ m2[-2] != -2 ||
+ m2[-3] != -3 ||
+ m2[-4] != -4
+ ) {
+ returnCode = returnCode or ERR_CONTAINERS
+ println("*** FAILURE ***\n")
+ }
+ }
+ /** INSANITY TEST */
+ var insanityFailed = true
+ try {
+ val hello = Xtruct()
+ hello.string_thing = "Hello2"
+ hello.byte_thing = 2
+ hello.i32_thing = 2
+ hello.i64_thing = 2
+ val goodbye = Xtruct()
+ goodbye.string_thing = "Goodbye4"
+ goodbye.byte_thing = 4.toByte()
+ goodbye.i32_thing = 4
+ goodbye.i64_thing = 4L
+ insane.userMap =
+ HashMap<Numberz, Long>().apply {
+ put(Numberz.EIGHT, 8L)
+ put(Numberz.FIVE, 5L)
+ }
+
+ insane.xtructs =
+ ArrayList<Xtruct>().apply {
+ add(goodbye)
+ add(hello)
+ }
+
+ print("testInsanity()")
+ val whoa: Map<Long, Map<Numberz, Insanity>> = testClient.testInsanity(insane)
+ print(" = {")
+ for (key in whoa.keys) {
+ val `val` = whoa[key]!!
+ print("$key => {")
+ for (k2 in `val`.keys) {
+ val v2 = `val`[k2]
+ print("$k2 => {")
+ val userMap = v2!!.userMap
+ print("{")
+ if (userMap != null) {
+ for (k3 in userMap.keys) {
+ print(k3.toString() + " => " + userMap[k3] + ", ")
+ }
+ }
+ print("}, ")
+ val xtructs = v2.xtructs
+ print("{")
+ if (xtructs != null) {
+ for ((string_thing, byte_thing, i32_thing, i64_thing) in xtructs) {
+ print("{\"$string_thing\", $byte_thing, $i32_thing, $i64_thing}, ")
+ }
+ }
+ print("}")
+ print("}, ")
+ }
+ print("}, ")
+ }
+ print("}\n")
+ if (whoa.size == 2 && whoa.containsKey(1L) && whoa.containsKey(2L)) {
+ val first_map = whoa[1L]!!
+ val second_map = whoa[2L]!!
+ if (first_map.size == 2 &&
+ first_map.containsKey(Numberz.TWO) &&
+ first_map.containsKey(Numberz.THREE) &&
+ second_map.size == 1 &&
+ second_map.containsKey(Numberz.SIX) &&
+ insane == first_map[Numberz.TWO] &&
+ insane == first_map[Numberz.THREE]
+ ) {
+ val six = second_map[Numberz.SIX]!!
+ // Cannot use "new Insanity().equals(six)" because as of now,
+ // struct/container
+ // fields with default requiredness have isset=false for local instances and
+ // yet
+ // received empty values from other languages like C++ have isset=true .
+ if ((six.userMap?.size ?: 0) == 0 && (six.xtructs?.size ?: 0) == 0) {
+ // OK
+ insanityFailed = false
+ }
+ }
+ }
+ } catch (ex: Exception) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ ex.printStackTrace(System.out)
+ insanityFailed = false
+ }
+ if (insanityFailed) {
+ returnCode = returnCode or ERR_STRUCTS
+ println("*** FAILURE ***\n")
+ }
+ /** EXECPTION TEST */
+ try {
+ print("testClient.testException(\"Xception\") =>")
+ testClient.testException("Xception")
+ print(" void\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ } catch (e: Xception) {
+ System.out.printf(" {%d, \"%s\"}\n", e.errorCode, e.message)
+ testClient = clientFactory()
+ }
+ try {
+ print("testClient.testException(\"TException\") =>")
+ testClient.testException("TException")
+ print(" void\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ } catch (e: TException) {
+ System.out.printf(" {\"%s\"}\n", e.message)
+ testClient = clientFactory()
+ }
+ try {
+ print("testClient.testException(\"success\") =>")
+ testClient.testException("success")
+ print(" void\n")
+ } catch (e: Exception) {
+ System.out.printf(" exception\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ }
+ /** MULTI EXCEPTION TEST */
+ try {
+ System.out.printf("testClient.testMultiException(\"Xception\", \"test 1\") =>")
+ testClient.testMultiException("Xception", "test 1")
+ print(" result\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ } catch (e: Xception) {
+ System.out.printf(" {%d, \"%s\"}\n", e.errorCode, e.message)
+ testClient = clientFactory()
+ }
+ try {
+ System.out.printf("testClient.testMultiException(\"Xception2\", \"test 2\") =>")
+ testClient.testMultiException("Xception2", "test 2")
+ print(" result\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ } catch (e: Xception2) {
+ System.out.printf(" {%d, {\"%s\"}}\n", e.errorCode, e.struct_thing!!.string_thing)
+ testClient = clientFactory()
+ }
+ try {
+ print("testClient.testMultiException(\"success\", \"test 3\") =>")
+ val result: Xtruct = testClient.testMultiException("success", "test 3")
+ System.out.printf(" {{\"%s\"}}\n", result.string_thing)
+ } catch (e: Exception) {
+ System.out.printf(" exception\n*** FAILURE ***\n")
+ returnCode = returnCode or ERR_EXCEPTIONS
+ }
+ /** ONEWAY TEST */
+ print("testOneway(3)...")
+ val startOneway = System.nanoTime()
+ testClient.testOneway(3)
+ val onewayElapsedMillis = (System.nanoTime() - startOneway) / 1000000
+ if (onewayElapsedMillis > 200) {
+ println(
+ "Oneway test took too long to execute failed: took " +
+ onewayElapsedMillis +
+ "ms"
+ )
+ println(
+ "oneway calls are 'fire and forget' and therefore should not cause blocking."
+ )
+ println(
+ "Some transports (HTTP) have a required response, and typically this failure"
+ )
+ println("means the transport response was delayed until after the execution")
+ println(
+ "of the RPC. The server should post the transport response immediately and"
+ )
+ println("before executing the RPC.")
+ println("*** FAILURE ***")
+ returnCode = returnCode or ERR_BASETYPES
+ } else {
+ println("Success - fire and forget only took " + onewayElapsedMillis + "ms")
+ }
+ val stop = System.nanoTime()
+ val tot = stop - start
+ println("Total time: " + tot / 1000 + "us")
+ if (timeMin == 0L || tot < timeMin) {
+ timeMin = tot
+ }
+ if (tot > timeMax) {
+ timeMax = tot
+ }
+ timeTot += tot
+ // transport.close()
+ } catch (x: Exception) {
+ System.out.printf("*** FAILURE ***\n")
+ x.printStackTrace()
+ returnCode = returnCode or ERR_UNKNOWN
+ }
+ }
+ val timeAvg = timeTot / numTests
+ println("Min time: " + timeMin / 1000 + "us")
+ println("Max time: " + timeMax / 1000 + "us")
+ println("Avg time: " + timeAvg / 1000 + "us")
+ try {
+ val json = TSerializer(TSimpleJSONProtocol.Factory()).toString(insane)
+ println("\nSample TSimpleJSONProtocol output:\n$json")
+ } catch (x: TException) {
+ println("*** FAILURE ***")
+ x.printStackTrace()
+ returnCode = returnCode or ERR_BASETYPES
+ }
+ exitProcess(returnCode)
+}
+
+private fun getTProtocol(protocol_type: String, transport: TTransport) =
+ when (protocol_type) {
+ "json", "multij" -> {
+ TJSONProtocol(transport)
+ }
+ "compact", "multic" -> {
+ TCompactProtocol(transport)
+ }
+ else -> {
+ TBinaryProtocol(transport)
+ }
+ }
+
+private fun checkTransportType(transport_type: String) {
+ when (transport_type) {
+ "buffered" -> {}
+ "framed" -> {}
+ "fastframed" -> {}
+ "http" -> {}
+ "zlib" -> {}
+ else -> {
+ throw Exception("Unknown transport type! $transport_type")
+ }
+ }
+}
+
+private fun checkProtocolType(protocol_type: String) {
+ when (protocol_type) {
+ "binary" -> {}
+ "compact" -> {}
+ "json" -> {}
+ "multi" -> {}
+ "multic" -> {}
+ "multij" -> {}
+ else -> {
+ throw Exception("Unknown protocol type! $protocol_type")
+ }
+ }
+}
+
+private fun getTTransport(
+ transport_type: String,
+ host: String,
+ port: Int,
+ http_client: Boolean,
+ ssl: Boolean,
+ socketTimeout: Int,
+ zlib: Boolean
+): TTransport {
+ when (transport_type) {
+ "http" -> {
+ val url = "http://$host:$port/test/service"
+ return if (http_client) {
+ THttpClient(url, HttpClients.createDefault())
+ } else {
+ THttpClient(url)
+ }
+ }
+ else -> {
+ val socket = if (ssl) {
+ TSSLTransportFactory.getClientSocket(host, port, socketTimeout)
+ } else {
+ println("using non-blocking socket $host:$port")
+ TNonblockingSocket(host, port, socketTimeout)
+ }
+ if (transport_type == "zlib") {
+ return TZlibTransport(socket)
+ } else {
+ val wrapped = when (transport_type) {
+ "buffered" -> {
+ socket
+ }
+ "framed" -> {
+ TFramedTransport(socket)
+ }
+ "fastframed" -> {
+ TFastFramedTransport(socket)
+ }
+ else -> {
+ socket
+ }
+ }
+ return if (zlib) {
+ TZlibTransport(wrapped)
+ } else {
+ wrapped
+ }
+ }
+ }
+ }
+}
diff --git a/lib/kotlin/cross-test-client/src/main/resources/logback.xml b/lib/kotlin/cross-test-client/src/main/resources/logback.xml
new file mode 100644
index 000000000..2a2fc0a58
--- /dev/null
+++ b/lib/kotlin/cross-test-client/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
+
diff --git a/lib/kotlin/cross-test-server/build.gradle.kts b/lib/kotlin/cross-test-server/build.gradle.kts
new file mode 100644
index 000000000..6b20de112
--- /dev/null
+++ b/lib/kotlin/cross-test-server/build.gradle.kts
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ kotlin("jvm") version "1.5.31"
+ id("com.ncorti.ktfmt.gradle") version "0.4.0"
+ java
+ application
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+ // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.1")
+ // https://mvnrepository.com/artifact/org.apache.thrift/libthrift
+ implementation("org.apache.thrift:libthrift:INCLUDED")
+ // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
+ implementation("ch.qos.logback:logback-classic:1.3.0-alpha14")
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
+}
+
+tasks {
+ application {
+ applicationName = "TestServer"
+ mainClass.set("org.apache.thrift.test.TestServerKt")
+ }
+
+ ktfmt {
+ kotlinLangStyle()
+ }
+
+ task<Exec>("compileThrift") {
+ val thriftBin = if (hasProperty("thrift.compiler")) {
+ file(property("thrift.compiler"))
+ } else {
+ project.rootDir.resolve("../../compiler/cpp/thrift")
+ }
+ val outputDir = layout.buildDirectory.dir("generated-sources")
+ doFirst {
+ mkdir(outputDir)
+ }
+ commandLine = listOf(
+ thriftBin.absolutePath,
+ "-gen",
+ "kotlin",
+ "-out",
+ outputDir.get().toString(),
+ project.rootDir.resolve("../../test/ThriftTest.thrift").absolutePath
+ )
+ group = LifecycleBasePlugin.BUILD_GROUP
+ }
+
+ compileKotlin {
+ dependsOn("compileThrift")
+ }
+}
+
+sourceSets["main"].java {
+ srcDir(layout.buildDirectory.dir("generated-sources"))
+}
diff --git a/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt
new file mode 100644
index 000000000..4bbdb6ab0
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestHandler.kt
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.thrift.test
+
+import java.nio.ByteBuffer
+import kotlinx.coroutines.delay
+import org.apache.thrift.TException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import thrift.test.Insanity
+import thrift.test.Numberz
+import thrift.test.ThriftTest
+import thrift.test.Xception
+import thrift.test.Xception2
+import thrift.test.Xtruct
+import thrift.test.Xtruct2
+
+class TestHandler : ThriftTest {
+
+ companion object {
+ private val logger: Logger = LoggerFactory.getLogger(TestHandler::class.java)
+ }
+
+ override suspend fun testVoid() {
+ logger.info("testVoid()\n")
+ }
+
+ override suspend fun testString(thing: String): String {
+ logger.info("testString(\"$thing\")\n")
+ return thing
+ }
+
+ override suspend fun testBool(thing: Boolean): Boolean {
+ logger.info("testBool($thing)\n")
+ return thing
+ }
+
+ override suspend fun testByte(thing: Byte): Byte {
+ logger.info("testByte($thing)\n")
+ return thing
+ }
+
+ override suspend fun testI32(thing: Int): Int {
+ logger.info("testI32($thing)\n")
+ return thing
+ }
+
+ override suspend fun testI64(thing: Long): Long {
+ logger.info("testI64($thing)\n")
+ return thing
+ }
+
+ override suspend fun testDouble(thing: Double): Double {
+ logger.info("testDouble($thing)\n")
+ return thing
+ }
+
+ override suspend fun testBinary(thing: ByteArray): ByteArray {
+ val buffer = ByteBuffer.wrap(thing)
+ val sb = StringBuilder(buffer.remaining() * 3)
+ buffer.mark()
+ var limit = 0 // limit output to keep the log size sane
+ while (buffer.remaining() > 0 && ++limit < 1024) {
+ sb.append(String.format("%02X ", buffer.get()))
+ }
+ if (buffer.remaining() > 0) {
+ sb.append("...") // indicate we have more date
+ }
+ logger.info("testBinary($sb)\n")
+ buffer.reset()
+ return buffer.array()
+ }
+
+ override suspend fun testStruct(thing: Xtruct): Xtruct {
+ logger.info(
+ """
+testStruct({"${thing.string_thing}", ${thing.byte_thing}, ${thing.i32_thing}, ${thing.i64_thing}})
+
+""".trimIndent()
+ )
+ return thing
+ }
+
+ override suspend fun testNest(thing: Xtruct2): Xtruct2 {
+ val thing2: Xtruct = thing.struct_thing!!
+ logger.info(
+ """
+testNest({${thing.byte_thing}, {"${thing2.string_thing}", ${thing2.byte_thing}, ${thing2.i32_thing}, ${thing2.i64_thing}}, ${thing.i32_thing}})
+
+""".trimIndent()
+ )
+ return thing
+ }
+
+ override suspend fun testMap(thing: Map<Int, Int>): Map<Int, Int> {
+ logger.info("testMap({")
+ logger.info("{}", thing)
+ logger.info("})\n")
+ return thing
+ }
+
+ override suspend fun testStringMap(thing: Map<String, String>): Map<String, String> {
+ logger.info("testStringMap({")
+ logger.info("{}", thing)
+ logger.info("})\n")
+ return thing
+ }
+
+ override suspend fun testSet(thing: Set<Int>): Set<Int> {
+ logger.info("testSet({")
+ var first = true
+ for (elem in thing) {
+ if (first) {
+ first = false
+ } else {
+ logger.info(", ")
+ }
+ logger.info("{}", elem)
+ }
+ logger.info("})\n")
+ return thing
+ }
+
+ override suspend fun testList(thing: List<Int>): List<Int> {
+ logger.info("testList({")
+ var first = true
+ for (elem in thing) {
+ if (first) {
+ first = false
+ } else {
+ logger.info(", ")
+ }
+ logger.info("{}", elem)
+ }
+ logger.info("})\n")
+ return thing
+ }
+
+ override suspend fun testEnum(thing: Numberz): Numberz {
+ logger.info("testEnum($thing)\n")
+ return thing
+ }
+
+ override suspend fun testTypedef(thing: Long): Long {
+ logger.info("testTypedef($thing)\n")
+ return thing
+ }
+
+ override suspend fun testMapMap(hello: Int): Map<Int, Map<Int, Int>> {
+ logger.info("testMapMap($hello)\n")
+ val mapmap: MutableMap<Int, Map<Int, Int>> = HashMap()
+ val pos = HashMap<Int, Int>()
+ val neg = HashMap<Int, Int>()
+ for (i in 1..4) {
+ pos[i] = i
+ neg[-i] = -i
+ }
+ mapmap[4] = pos
+ mapmap[-4] = neg
+ return mapmap
+ }
+
+ override suspend fun testInsanity(argument: Insanity): Map<Long, Map<Numberz, Insanity>> {
+ logger.info("testInsanity()\n")
+ val firstMap = mutableMapOf<Numberz, Insanity>()
+ val secondMap = mutableMapOf<Numberz, Insanity>()
+ firstMap[Numberz.TWO] = argument
+ firstMap[Numberz.THREE] = argument
+ val looney = Insanity()
+ secondMap[Numberz.SIX] = looney
+ val insane: MutableMap<Long, Map<Numberz, Insanity>> = HashMap()
+ insane[1L] = firstMap
+ insane[2L] = secondMap
+ return insane
+ }
+
+ override suspend fun testMulti(
+ arg0: Byte,
+ arg1: Int,
+ arg2: Long,
+ arg3: Map<Short, String>,
+ arg4: Numberz,
+ arg5: Long
+ ): Xtruct {
+ logger.info("testMulti()\n")
+ val hello = Xtruct()
+ hello.string_thing = "Hello2"
+ hello.byte_thing = arg0
+ hello.i32_thing = arg1
+ hello.i64_thing = arg2
+ return hello
+ }
+
+ @Throws(Xception::class, TException::class)
+ override suspend fun testException(arg: String) {
+ logger.info("testException($arg)\n")
+ when (arg) {
+ "Xception" -> {
+ val x = Xception()
+ x.errorCode = 1001
+ x.setFieldValue(Xception._Fields.MESSAGE, arg)
+ throw x
+ }
+ "TException" -> {
+ // Unspecified exception should yield a TApplicationException on client side
+ throw RuntimeException(arg)
+ }
+ else -> {
+ val result = Xtruct()
+ result.string_thing = arg
+ }
+ }
+ return
+ }
+
+ @Throws(Xception::class, Xception2::class)
+ override suspend fun testMultiException(arg0: String, arg1: String): Xtruct {
+ logger.info("testMultiException($arg0, $arg1)\n")
+ if (arg0 == "Xception") {
+ val x = Xception()
+ x.errorCode = 1001
+ x.setFieldValue(Xception._Fields.MESSAGE, "This is an Xception")
+ throw x
+ } else if (arg0 == "Xception2") {
+ val x = Xception2()
+ x.errorCode = 2002
+ x.struct_thing = Xtruct().apply { string_thing = "This is an Xception2" }
+ throw x
+ }
+ val result = Xtruct()
+ result.string_thing = arg1
+ return result
+ }
+
+ override suspend fun testOneway(secondsToSleep: Int) {
+ logger.info("testOneway($secondsToSleep) => sleeping...")
+ try {
+ delay(secondsToSleep * 1000L)
+ logger.info("Done sleeping!")
+ } catch (ie: InterruptedException) {
+ throw RuntimeException(ie)
+ }
+ }
+}
diff --git a/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt
new file mode 100644
index 000000000..b04548d79
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/kotlin/org/apache/thrift/test/TestServer.kt
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.thrift.test
+
+import kotlin.system.exitProcess
+import kotlinx.coroutines.GlobalScope
+import org.apache.thrift.TException
+import org.apache.thrift.TMultiplexedProcessor
+import org.apache.thrift.protocol.TBinaryProtocol
+import org.apache.thrift.protocol.TCompactProtocol
+import org.apache.thrift.protocol.TJSONProtocol
+import org.apache.thrift.protocol.TProtocol
+import org.apache.thrift.protocol.TProtocolFactory
+import org.apache.thrift.server.ServerContext
+import org.apache.thrift.server.TNonblockingServer
+import org.apache.thrift.server.TServer
+import org.apache.thrift.server.TServerEventHandler
+import org.apache.thrift.server.TSimpleServer
+import org.apache.thrift.server.TThreadPoolServer
+import org.apache.thrift.server.TThreadedSelectorServer
+import org.apache.thrift.transport.TNonblockingServerSocket
+import org.apache.thrift.transport.TNonblockingServerSocket.NonblockingAbstractServerSocketArgs
+import org.apache.thrift.transport.TSSLTransportFactory
+import org.apache.thrift.transport.TServerSocket
+import org.apache.thrift.transport.TServerSocket.ServerSocketTransportArgs
+import org.apache.thrift.transport.TTransport
+import org.apache.thrift.transport.TTransportFactory
+import org.apache.thrift.transport.TZlibTransport
+import org.apache.thrift.transport.layered.TFastFramedTransport
+import org.apache.thrift.transport.layered.TFramedTransport
+import thrift.test.SecondService
+import thrift.test.SecondServiceProcessor
+import thrift.test.ThriftTestProcessor
+
+object TestServer {
+
+ // Multiplexed Protocol Support Details:
+ //
+ // For multiplexed testing we always use binary protocol underneath.
+ //
+ // "ThriftTest" named service implements "ThriftTest" from ThriftTest.thrift
+ // "SecondService" named service implements "SecondService" from ThriftTest.thrift
+ // In addition, to support older non-multiplexed clients using the same concrete protocol
+ // the multiplexed processor is taught to use "ThriftTest" if the incoming request has no
+ // multiplexed call name decoration.
+ internal class SecondHandler : SecondService {
+ @Throws(TException::class)
+ override suspend fun secondtestString(thing: String): String {
+ return "testString(\"$thing\")"
+ }
+ }
+
+ internal class TestServerContext(var connectionId: Int) : ServerContext {
+
+ override fun <T> unwrap(iface: Class<T>): T {
+ try {
+ return if (isWrapperFor(iface)) {
+ iface.cast(this)
+ } else {
+ throw RuntimeException("The context is not a wrapper for " + iface.name)
+ }
+ } catch (e: Exception) {
+ throw RuntimeException(
+ "The context is not a wrapper and does not implement the interface"
+ )
+ }
+ }
+
+ override fun isWrapperFor(iface: Class<*>): Boolean {
+ return iface.isInstance(this)
+ }
+ }
+
+ internal class TestServerEventHandler() : TServerEventHandler {
+ private var nextConnectionId = 1
+ override fun preServe() {
+ println(
+ "TServerEventHandler.preServe - called only once before server starts accepting connections"
+ )
+ }
+
+ override fun createContext(input: TProtocol, output: TProtocol): ServerContext {
+ // we can create some connection level data which is stored while connection is alive &
+ // served
+ val ctx = TestServerContext(nextConnectionId++)
+ println(
+ "TServerEventHandler.createContext - connection #" +
+ ctx.connectionId +
+ " established"
+ )
+ return ctx
+ }
+
+ override fun deleteContext(
+ serverContext: ServerContext,
+ input: TProtocol,
+ output: TProtocol
+ ) {
+ val ctx = serverContext.unwrap(TestServerContext::class.java)
+ println(
+ "TServerEventHandler.deleteContext - connection #" +
+ ctx.connectionId +
+ " terminated"
+ )
+ }
+
+ override fun processContext(
+ serverContext: ServerContext,
+ inputTransport: TTransport,
+ outputTransport: TTransport
+ ) {
+ val ctx = serverContext.unwrap(TestServerContext::class.java)
+ println(
+ "TServerEventHandler.processContext - connection #" +
+ ctx.connectionId +
+ " is ready to process next request"
+ )
+ }
+ }
+}
+
+fun main(args: Array<String>) {
+ try {
+ var port = 9090
+ var ssl = false
+ var zlib = false
+ var transportType = "buffered"
+ var protocolType = "binary"
+// var serverType = "thread-pool"
+ var serverType = "nonblocking"
+ val domainSocket = ""
+ var stringLimit: Long = -1
+ var containerLimit: Long = -1
+ try {
+ for (i in args.indices) {
+ if (args[i].startsWith("--port")) {
+ port = Integer.valueOf(args[i].split("=").toTypedArray()[1])
+ } else if (args[i].startsWith("--server-type")) {
+ serverType = args[i].split("=").toTypedArray()[1]
+ serverType.trim { it <= ' ' }
+ } else if (args[i].startsWith("--port")) {
+ port = args[i].split("=").toTypedArray()[1].toInt()
+ } else if (args[i].startsWith("--protocol")) {
+ protocolType = args[i].split("=").toTypedArray()[1]
+ protocolType.trim { it <= ' ' }
+ } else if (args[i].startsWith("--transport")) {
+ transportType = args[i].split("=").toTypedArray()[1]
+ transportType.trim { it <= ' ' }
+ } else if (args[i] == "--ssl") {
+ ssl = true
+ } else if (args[i] == "--zlib") {
+ zlib = true
+ } else if (args[i].startsWith("--string-limit")) {
+ stringLimit = args[i].split("=").toTypedArray()[1].toLong()
+ } else if (args[i].startsWith("--container-limit")) {
+ containerLimit = args[i].split("=").toTypedArray()[1].toLong()
+ } else if (args[i] == "--help") {
+ println("Allowed options:")
+ println(" --help\t\t\tProduce help message")
+ println(" --port=arg (=$port)\tPort number to connect")
+ println(
+ " --transport=arg (=$transportType)\n\t\t\t\tTransport: buffered, framed, fastframed, zlib"
+ )
+ println(
+ " --protocol=arg (=$protocolType)\tProtocol: binary, compact, json, multi, multic, multij"
+ )
+ println(" --ssl\t\t\tEncrypted Transport using SSL")
+ println(" --zlib\t\t\tCompressed Transport using Zlib")
+ println(
+ " --server-type=arg (=$serverType)\n\t\t\t\tType of server: simple, thread-pool, nonblocking, threaded-selector"
+ )
+ println(" --string-limit=arg (=$stringLimit)\tString read length limit")
+ println(
+ " --container-limit=arg (=$containerLimit)\tContainer read length limit"
+ )
+ exitProcess(0)
+ }
+ }
+ } catch (e: Exception) {
+ System.err.println("Can not parse arguments! See --help")
+ exitProcess(1)
+ }
+ try {
+ when (serverType) {
+ "simple" -> {}
+ "thread-pool" -> {}
+ "nonblocking" -> {
+ if (ssl) {
+ throw Exception("SSL is not supported over nonblocking servers!")
+ }
+ }
+ "threaded-selector" -> {
+ if (ssl) {
+ throw Exception("SSL is not supported over nonblocking servers!")
+ }
+ }
+ else -> {
+ throw Exception("Unknown server type! $serverType")
+ }
+ }
+ when (protocolType) {
+ "binary" -> {}
+ "compact" -> {}
+ "json" -> {}
+ "multi" -> {}
+ "multic" -> {}
+ "multij" -> {}
+ else -> {
+ throw Exception("Unknown protocol type! $protocolType")
+ }
+ }
+ when (transportType) {
+ "buffered" -> {}
+ "framed" -> {}
+ "fastframed" -> {}
+ "zlib" -> {}
+ else -> {
+ throw Exception("Unknown transport type! $transportType")
+ }
+ }
+ } catch (e: Exception) {
+ System.err.println("Error: " + e.message)
+ exitProcess(1)
+ }
+
+ // Processors
+ val testHandler = TestHandler()
+ val testProcessor = ThriftTestProcessor(testHandler, scope = GlobalScope)
+ val secondHandler = TestServer.SecondHandler()
+ val secondProcessor = SecondServiceProcessor(secondHandler, scope = GlobalScope)
+
+ // Protocol factory
+ val tProtocolFactory: TProtocolFactory =
+ when (protocolType) {
+ "json", "multij" -> {
+ TJSONProtocol.Factory()
+ }
+ "compact", "multic" -> {
+ TCompactProtocol.Factory(stringLimit, containerLimit)
+ }
+ else -> { // also covers multi
+ TBinaryProtocol.Factory(stringLimit, containerLimit)
+ }
+ }
+ val tTransportFactory: TTransportFactory =
+ when (transportType) {
+ "framed" -> {
+ TFramedTransport.Factory()
+ }
+ "fastframed" -> {
+ TFastFramedTransport.Factory()
+ }
+ "zlib" -> {
+ TZlibTransport.Factory()
+ }
+ else -> { // .equals("buffered") => default value
+ TTransportFactory()
+ }
+ }
+ val serverEngine: TServer
+ // If we are multiplexing services in one server...
+ val multiplexedProcessor = TMultiplexedProcessor()
+ multiplexedProcessor.registerDefault(testProcessor)
+ multiplexedProcessor.registerProcessor("ThriftTest", testProcessor)
+ multiplexedProcessor.registerProcessor("SecondService", secondProcessor)
+ if (serverType == "nonblocking" || serverType == "threaded-selector") {
+ // Nonblocking servers
+ val tNonblockingServerSocket =
+ TNonblockingServerSocket(NonblockingAbstractServerSocketArgs().port(port))
+ if (serverType.contains("nonblocking")) {
+ // Nonblocking Server
+ val tNonblockingServerArgs = TNonblockingServer.Args(tNonblockingServerSocket)
+ tNonblockingServerArgs.processor(
+ if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+ )
+ tNonblockingServerArgs.protocolFactory(tProtocolFactory)
+ tNonblockingServerArgs.transportFactory(tTransportFactory)
+ serverEngine = TNonblockingServer(tNonblockingServerArgs)
+ } else { // server_type.equals("threaded-selector")
+ // ThreadedSelector Server
+ val tThreadedSelectorServerArgs =
+ TThreadedSelectorServer.Args(tNonblockingServerSocket)
+ tThreadedSelectorServerArgs.processor(
+ if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+ )
+ tThreadedSelectorServerArgs.protocolFactory(tProtocolFactory)
+ tThreadedSelectorServerArgs.transportFactory(tTransportFactory)
+ serverEngine = TThreadedSelectorServer(tThreadedSelectorServerArgs)
+ }
+ } else {
+ // Blocking servers
+
+ // SSL socket
+ val tServerSocket: TServerSocket = if (ssl) {
+ TSSLTransportFactory.getServerSocket(port, 0)
+ } else {
+ TServerSocket(ServerSocketTransportArgs().port(port))
+ }
+ if (serverType == "simple") {
+ // Simple Server
+ val tServerArgs = TServer.Args(tServerSocket)
+ tServerArgs.processor(
+ if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+ )
+ tServerArgs.protocolFactory(tProtocolFactory)
+ tServerArgs.transportFactory(tTransportFactory)
+ serverEngine = TSimpleServer(tServerArgs)
+ } else { // server_type.equals("threadpool")
+ // ThreadPool Server
+ val tThreadPoolServerArgs = TThreadPoolServer.Args(tServerSocket)
+ tThreadPoolServerArgs.processor(
+ if (protocolType.startsWith("multi")) multiplexedProcessor else testProcessor
+ )
+ tThreadPoolServerArgs.protocolFactory(tProtocolFactory)
+ tThreadPoolServerArgs.transportFactory(tTransportFactory)
+ serverEngine = TThreadPoolServer(tThreadPoolServerArgs)
+ }
+ }
+
+ // Set server event handler
+ serverEngine.setServerEventHandler(TestServer.TestServerEventHandler())
+
+ // Run it
+ println(
+ "Starting the " +
+ (if (ssl) "ssl server" else "server") +
+ " [" +
+ protocolType +
+ "/" +
+ transportType +
+ "/" +
+ serverType +
+ "] on " +
+ if (domainSocket === "") "port $port" else "unix socket $domainSocket"
+ )
+ serverEngine.serve()
+ } catch (x: Exception) {
+ x.printStackTrace()
+ }
+ println("done.")
+}
diff --git a/lib/kotlin/cross-test-server/src/main/resources/logback.xml b/lib/kotlin/cross-test-server/src/main/resources/logback.xml
new file mode 100644
index 000000000..98309d78c
--- /dev/null
+++ b/lib/kotlin/cross-test-server/src/main/resources/logback.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
diff --git a/lib/kotlin/settings.gradle.kts b/lib/kotlin/settings.gradle.kts
new file mode 100644
index 000000000..f412707d4
--- /dev/null
+++ b/lib/kotlin/settings.gradle.kts
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+rootProject.name = "libthrift-kotlin"
+
+include("cross-test-client", "cross-test-server")
+
+includeBuild("../java")
diff --git a/test/crossrunner/run.py b/test/crossrunner/run.py
index bb06d25ef..126b7ec88 100644
--- a/test/crossrunner/run.py
+++ b/test/crossrunner/run.py
@@ -243,7 +243,11 @@ def run_test(testdir, logdir, test_dict, max_retry, async_mode=True):
# kill them off; if we didn't kill them off, something else
# happened (crashed?)
if test.server.stop_signal != 0:
- if sv.killed or sv.returncode > 0:
+ # for bash scripts, 128+N is the exit code for signal N, since we are sending
+ # DEFAULT_SIGNAL=1, 128 + 1 is the expected err code
+ # http://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
+ allowed_return_code = set([-1, 0, 128 + 1])
+ if sv.killed or sv.returncode not in allowed_return_code:
result |= RESULT_ERROR
else:
if not sv.killed:
diff --git a/test/known_failures_Linux.json b/test/known_failures_Linux.json
index 53a51391a..74ae53f7e 100644
--- a/test/known_failures_Linux.json
+++ b/test/known_failures_Linux.json
@@ -569,6 +569,7 @@
"java-php_multij-json_buffered-ip",
"java-php_multij-json_fastframed-framed-ip",
"java-php_multij-json_framed-ip",
+ "kotlin-netstd_binary_framed-ip",
"netstd-cl_binary_buffered-ip",
"netstd-cl_binary_framed-ip",
"netstd-cpp_binary_buffered-ip",
@@ -653,6 +654,7 @@
"netstd-java_json_framed-fastframed-ip-ssl",
"netstd-java_json_framed-ip",
"netstd-java_json_framed-ip-ssl",
+ "netstd-kotlin_binary_framed-ip",
"netstd-lua_binary_buffered-ip",
"netstd-lua_binary_framed-ip",
"netstd-lua_compact_buffered-ip",
diff --git a/test/tests.json b/test/tests.json
index 3563dc9ab..e897b3917 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -171,6 +171,41 @@
"workdir": "../lib/java"
},
{
+ "name": "kotlin",
+ "join_args": false,
+ "server": {
+ "delay": 15,
+ "command": [
+ "cross-test-server/build/install/TestServer/bin/TestServer"
+ ],
+ "protocols": [
+ "binary"
+ ]
+ },
+ "client": {
+ "timeout": 13,
+ "command": [
+ "cross-test-client/build/install/TestClient/bin/TestClient"
+ ],
+ "transports": [
+ "framed"
+ ],
+ "protocols": [
+ "binary"
+ ]
+ },
+ "transports": [
+ "framed"
+ ],
+ "sockets": [
+ "ip"
+ ],
+ "protocols": [
+ "binary"
+ ],
+ "workdir": "../lib/kotlin"
+ },
+ {
"name": "nodejs",
"env": {
"NODE_PATH": "../lib"