THRIFT-3299 Create an Apache Thrift language binding for Dart (dartlang.org).
Client: Dart
Patch: Mark Erickson <mark.erickson@workiva.com>
This closes #608
diff --git a/.gitignore b/.gitignore
index e0c7a26..44e630a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,6 +145,10 @@
/lib/d/test/serialization_benchmark
/lib/d/test/transport_test
/lib/d/unittest/
+/lib/dart/**/.packages
+/lib/dart/**/packages
+/lib/dart/**/.pub/
+/lib/dart/**/pubspec.lock
/lib/delphi/src/*.dcu
/lib/delphi/test/*.identcache
/lib/delphi/test/*.local
@@ -235,6 +239,10 @@
/test/cpp/StressTestNonBlocking
/test/cpp/TestClient
/test/cpp/TestServer
+/test/dart/**/.packages
+/test/dart/**/packages
+/test/dart/**/.pub/
+/test/dart/**/pubspec.lock
/test/log/
/test/test.log
/test/erl/.eunit/
@@ -260,6 +268,10 @@
/tutorial/csharp/CsharpServer/bin
/tutorial/csharp/CsharpClient/obj
/tutorial/csharp/CsharpClient/bin
+/tutorial/dart/**/.packages
+/tutorial/dart/**/packages
+/tutorial/dart/**/.pub/
+/tutorial/dart/**/pubspec.lock
/tutorial/delphi/*.dsk
/tutorial/delphi/*.local
/tutorial/delphi/*.tvsconfig
diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt
index bc6591c..2d0c3f0 100644
--- a/compiler/cpp/CMakeLists.txt
+++ b/compiler/cpp/CMakeLists.txt
@@ -47,7 +47,7 @@
add_library(libparse STATIC ${libparse_SOURCES})
# Create the thrift compiler
-set( thrift_SOURCES
+set( thrift_SOURCES
src/main.cc
src/md5.c
src/generate/t_generator.cc
@@ -100,6 +100,7 @@
THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" ON)
THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON)
THRIFT_ADD_COMPILER(as3 "Enable compiler for ActionScript 3" ON)
+THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON)
THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON)
THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" ON)
THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON)
diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am
index 38f9f9d..2aa6913 100644
--- a/compiler/cpp/Makefile.am
+++ b/compiler/cpp/Makefile.am
@@ -74,6 +74,7 @@
src/generate/t_java_generator.cc \
src/generate/t_json_generator.cc \
src/generate/t_as3_generator.cc \
+ src/generate/t_dart_generator.cc \
src/generate/t_haxe_generator.cc \
src/generate/t_csharp_generator.cc \
src/generate/t_py_generator.cc \
diff --git a/compiler/cpp/src/generate/t_dart_generator.cc b/compiler/cpp/src/generate/t_dart_generator.cc
new file mode 100644
index 0000000..147ca13
--- /dev/null
+++ b/compiler/cpp/src/generate/t_dart_generator.cc
@@ -0,0 +1,2361 @@
+/*
+ * 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 <sstream>
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <vector>
+#include <cctype>
+
+#include <sys/stat.h>
+#include <stdexcept>
+
+#include "platform.h"
+#include "t_oop_generator.h"
+
+using std::map;
+using std::ofstream;
+using std::ostringstream;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+static const string endl = "\n"; // avoid ostream << std::endl flushes
+static const string endl2 = "\n\n";
+
+/**
+ * Use the current Thrift version for static libraries. When releasing, update
+ * the version in these files.
+ * - lib/dart/pubspec.yaml
+ * - test/dart/test_client/pubspec.yaml
+ * - tutorial/dart/client/pubspec.yaml
+ * - tutorial/dart/console_client/pubspec.yaml
+ * - tutorial/dart/server/pubspec.yaml
+ * See https://thrift.apache.org/docs/committers/HowToVersion
+ */
+static const string dart_thrift_version = THRIFT_VERSION;
+
+/* forward declarations */
+string initial_caps_to_underscores(string name);
+
+/**
+ * Dart code generator
+ *
+ */
+class t_dart_generator : public t_oop_generator {
+public:
+ t_dart_generator(t_program* program,
+ const std::map<std::string, std::string>& parsed_options,
+ const std::string& option_string)
+ : t_oop_generator(program) {
+ (void)option_string;
+ std::map<std::string, std::string>::const_iterator iter;
+
+ iter = parsed_options.find("library_name");
+ if (iter != parsed_options.end()) {
+ library_name_ = (iter->second);
+ } else {
+ library_name_ = "";
+ }
+
+ out_dir_base_ = "gen-dart";
+ }
+
+ void scope_up(std::ostream& out, std::string prefix=" ") {
+ out << prefix << "{" << endl;
+ indent_up();
+ }
+
+ void scope_down(std::ostream& out, std::string postfix=endl) {
+ indent_down();
+ indent(out) << "}" << postfix;
+ }
+
+ string replace_all(string contents, string search, string repl) {
+ string str(contents);
+
+ size_t slen = search.length();
+ size_t rlen = repl.length();
+ size_t incr = (rlen > 0) ? rlen : 1;
+
+ if (slen > 0) {
+ size_t found = str.find(search);
+ while ((found != string::npos) && (found < str.length())) {
+ str.replace(found, slen, repl);
+ found = str.find(search, found + incr);
+ }
+ }
+
+ return str;
+ }
+
+
+ /**
+ * Init and close methods
+ */
+
+ void init_generator();
+ void close_generator();
+
+ void export_class_to_library(string file_name, string class_name);
+
+ void generate_dart_library();
+ void generate_dart_pubspec();
+
+ void generate_consts(std::vector<t_const*> consts);
+
+ /**
+ * Program-level generation functions
+ */
+
+ void generate_typedef(t_typedef* ttypedef);
+ void generate_enum(t_enum* tenum);
+ void generate_struct(t_struct* tstruct);
+ void generate_xception(t_struct* txception);
+ void generate_service(t_service* tservice);
+
+ void print_const_value(std::ofstream& out,
+ std::string name,
+ t_type* type,
+ t_const_value* value,
+ bool in_static,
+ bool defval = false);
+ std::string render_const_value(ofstream& out,
+ std::string name,
+ t_type* type,
+ t_const_value* value);
+
+ /**
+ * Service-level generation functions
+ */
+
+ void generate_dart_struct(t_struct* tstruct, bool is_exception);
+
+ void generate_dart_struct_definition(std::ofstream& out,
+ t_struct* tstruct,
+ bool is_xception = false,
+ bool is_result = false,
+ string export_file_name = "");
+ void generate_dart_struct_reader(std::ofstream& out, t_struct* tstruct);
+ void generate_dart_validator(std::ofstream& out, t_struct* tstruct);
+ void generate_dart_struct_result_writer(std::ofstream& out, t_struct* tstruct);
+ void generate_dart_struct_writer(std::ofstream& out, t_struct* tstruct);
+ void generate_dart_struct_tostring(std::ofstream& out, t_struct* tstruct);
+ std::string get_dart_type_string(t_type* type);
+ void generate_generic_field_getters(std::ofstream& out, t_struct* tstruct);
+ void generate_generic_field_setters(std::ofstream& out, t_struct* tstruct);
+ void generate_generic_isset_method(std::ofstream& out, t_struct* tstruct);
+ void generate_dart_bean_boilerplate(std::ofstream& out, t_struct* tstruct);
+
+ void generate_function_helpers(t_function* tfunction);
+ std::string get_cap_name(std::string name);
+ std::string get_field_name(std::string name);
+ std::string get_args_class_name(std::string name);
+ std::string get_result_class_name(std::string name);
+ std::string get_file_name(std::string name);
+ std::string generate_isset_check(t_field* field);
+ std::string generate_isset_check(std::string field);
+ void generate_isset_set(ofstream& out, t_field* field);
+
+ void generate_service_interface(t_service* tservice);
+ void generate_service_helpers(t_service* tservice);
+ void generate_service_client(t_service* tservice);
+ void generate_service_server(t_service* tservice);
+ void generate_process_function(t_service* tservice, t_function* tfunction);
+
+ /**
+ * Serialization constructs
+ */
+
+ void generate_deserialize_field(std::ofstream& out, t_field* tfield, std::string prefix = "");
+
+ void generate_deserialize_struct(std::ofstream& out, t_struct* tstruct, std::string prefix = "");
+
+ void generate_deserialize_container(std::ofstream& out, t_type* ttype, std::string prefix = "");
+
+ void generate_deserialize_set_element(std::ofstream& out, t_set* tset, std::string prefix = "");
+
+ void generate_deserialize_map_element(std::ofstream& out, t_map* tmap, std::string prefix = "");
+
+ void generate_deserialize_list_element(std::ofstream& out,
+ t_list* tlist,
+ std::string prefix = "");
+
+ void generate_serialize_field(std::ofstream& out, t_field* tfield, std::string prefix = "");
+
+ void generate_serialize_struct(std::ofstream& out, t_struct* tstruct, std::string prefix = "");
+
+ void generate_serialize_container(std::ofstream& out, t_type* ttype, std::string prefix = "");
+
+ void generate_serialize_map_element(std::ofstream& out,
+ t_map* tmap,
+ std::string iter,
+ std::string map);
+
+ void generate_serialize_set_element(std::ofstream& out, t_set* tmap, std::string iter);
+
+ void generate_serialize_list_element(std::ofstream& out, t_list* tlist, std::string iter);
+
+ void generate_dart_doc(std::ofstream& out, t_doc* tdoc);
+
+ void generate_dart_doc(std::ofstream& out, t_function* tdoc);
+
+ /**
+ * Helper rendering functions
+ */
+
+ std::string dart_library(string file_name);
+ std::string service_imports();
+ std::string dart_thrift_imports();
+ std::string type_name(t_type* ttype);
+ std::string base_type_name(t_base_type* tbase);
+ std::string declare_field(t_field* tfield, bool init = false);
+ std::string function_signature(t_function* tfunction);
+ std::string argument_list(t_struct* tstruct);
+ std::string type_to_enum(t_type* ttype);
+ std::string get_enum_class_name(t_type* type);
+
+ bool type_can_be_null(t_type* ttype) {
+ ttype = get_true_type(ttype);
+
+ return ttype->is_container() || ttype->is_struct() || ttype->is_xception()
+ || ttype->is_string();
+ }
+
+ std::string constant_name(std::string name);
+
+private:
+ std::ofstream f_service_;
+
+ std::string library_name_;
+
+ std::string base_dir_;
+ std::string src_dir_;
+ std::string library_exports_;
+};
+
+/**
+ * Prepares for file generation by opening up the necessary file output
+ * streams.
+ *
+ * @param tprogram The program to generate
+ */
+void t_dart_generator::init_generator() {
+ MKDIR(get_out_dir().c_str());
+
+ if (library_name_.empty()) {
+ library_name_ = program_->get_namespace("dart");
+ }
+ if (library_name_.empty()) {
+ library_name_ = program_->get_name();
+ }
+ library_name_ = replace_all(library_name_, ".", "_");
+
+ string subdir = get_out_dir() + "/" + library_name_;
+ MKDIR(subdir.c_str());
+
+ base_dir_ = subdir;
+ subdir += "/lib";
+ MKDIR(subdir.c_str());
+
+ subdir += "/src";
+ MKDIR(subdir.c_str());
+ src_dir_ = subdir;
+}
+
+/**
+ * The Dart library
+ *
+ * @return String of the library, e.g. "library myservice;"
+ */
+string t_dart_generator::dart_library(string file_name) {
+ string out = "library ";
+ if (!library_name_.empty()) {
+ out += library_name_;
+ }
+ if (!file_name.empty()) {
+ out += ".src." + file_name;
+ }
+ return out + ";\n";
+}
+
+/**
+ * Prints imports for services
+ *
+ * @return List of imports for services
+ */
+string t_dart_generator::service_imports() {
+ return "import 'dart:async';" + endl;
+}
+
+/**
+ * Prints standard dart imports
+ *
+ * @return List of imports necessary for thrift
+ */
+string t_dart_generator::dart_thrift_imports() {
+ string imports = "import 'dart:typed_data' show Uint8List;" + endl +
+ "import 'package:thrift/thrift.dart';" + endl +
+ "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl;
+
+ // add imports for included thrift files
+ const vector<t_program*>& includes = program_->get_includes();
+ for (size_t i = 0; i < includes.size(); ++i) {
+ string include_name = includes[i]->get_namespace("dart");
+ imports += "import 'package:" + include_name + "/" + include_name + ".dart';" + endl;
+ }
+
+ return imports;
+}
+
+/**
+ * Not used
+ */
+void t_dart_generator::close_generator() {
+ generate_dart_library();
+ generate_dart_pubspec();
+}
+
+void t_dart_generator::generate_dart_library() {
+ string f_library_name = base_dir_ + "/lib/" + library_name_ + ".dart";
+ ofstream f_library;
+ f_library.open(f_library_name.c_str());
+
+ f_library << autogen_comment() << endl;
+ f_library << "library " << library_name_ << ";" << endl2;
+ f_library << library_exports_;
+
+ f_library.close();
+}
+
+void t_dart_generator::export_class_to_library(string file_name, string class_name) {
+ library_exports_ += "export 'src/" + file_name + ".dart' show " + class_name + ";" + endl;
+}
+
+void t_dart_generator::generate_dart_pubspec() {
+ string f_pubspec_name = base_dir_ + "/pubspec.yaml";
+ ofstream f_pubspec;
+ f_pubspec.open(f_pubspec_name.c_str());
+
+ indent(f_pubspec) << "name: " << library_name_ << endl;
+ indent(f_pubspec) << "version: 0.0.1" << endl;
+ indent(f_pubspec) << "description: Autogenerated by Thrift Compiler" << endl;
+ f_pubspec << endl;
+
+ indent(f_pubspec) << "environment:" << endl;
+ indent_up();
+ indent(f_pubspec) << "sdk: ^1.12.0" << endl;
+ indent_down();
+ f_pubspec << endl;
+
+ indent(f_pubspec) << "dependencies:" << endl;
+ indent_up();
+ indent(f_pubspec) << "thrift: # ^" << dart_thrift_version << endl;
+ indent_up();
+ indent(f_pubspec) << "path: ../../../../lib/dart" << endl;
+ indent_down();
+
+ // add included thrift files as dependencies
+ const vector<t_program*>& includes = program_->get_includes();
+ for (size_t i = 0; i < includes.size(); ++i) {
+ string include_name = includes[i]->get_namespace("dart");
+ indent(f_pubspec) << include_name << ":" << endl;
+ indent_up();
+ indent(f_pubspec) << "path: ../" << include_name << endl;
+ indent_down();
+ }
+
+ indent_down();
+ f_pubspec << endl;
+
+ f_pubspec.close();
+}
+
+/**
+ * Not used
+ *
+ * @param ttypedef The type definition
+ */
+void t_dart_generator::generate_typedef(t_typedef* ttypedef) {
+ (void)ttypedef;
+}
+
+/**
+ * Enums are a class with a set of static constants.
+ *
+ * @param tenum The enumeration
+ */
+void t_dart_generator::generate_enum(t_enum* tenum) {
+ // Make output file
+ string file_name = get_file_name(tenum->get_name());
+
+ string f_enum_name = src_dir_ + "/" + file_name + ".dart";
+ ofstream f_enum;
+ f_enum.open(f_enum_name.c_str());
+
+ // Comment and add library
+ f_enum << autogen_comment() << dart_library(file_name) << endl;
+
+ string class_name = tenum->get_name();
+ export_class_to_library(file_name, class_name);
+ f_enum << "class " << class_name;
+ scope_up(f_enum);
+
+ vector<t_enum_value*> constants = tenum->get_constants();
+ vector<t_enum_value*>::iterator c_iter;
+ for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
+ int value = (*c_iter)->get_value();
+ indent(f_enum) << "static const int " << (*c_iter)->get_name() << " = " << value << ";"
+ << endl;
+ }
+
+ // Create a static Set with all valid values for this enum
+ f_enum << endl;
+
+ indent(f_enum) << "static final Set<int> VALID_VALUES = new Set.from([" << endl;
+ indent_up();
+ bool firstValue = true;
+ for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
+ // populate set
+ indent(f_enum) << (firstValue ? "" : ", ");
+ f_enum << (*c_iter)->get_name() << endl;
+ firstValue = false;
+ }
+ indent_down();
+ indent(f_enum) << "]);" << endl;
+
+ indent(f_enum) << "static final Map<int, String> VALUES_TO_NAMES = {" << endl;
+ indent_up();
+ firstValue = true;
+ for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
+ indent(f_enum) << (firstValue ? "" : ", ");
+ f_enum << (*c_iter)->get_name() << ": '" << (*c_iter)->get_name() << "'" << endl;
+ firstValue = false;
+ }
+ indent_down();
+ indent(f_enum) << "};" << endl;
+
+ scope_down(f_enum); // end class
+
+ f_enum.close();
+}
+
+/**
+ * Generates a class that holds all the constants.
+ */
+void t_dart_generator::generate_consts(std::vector<t_const*> consts) {
+ if (consts.empty()) {
+ return;
+ }
+
+ string class_name = get_cap_name(program_name_) + "Constants";
+ string file_name = get_file_name(class_name);
+
+ string f_consts_name = src_dir_ + "/" + file_name + ".dart";
+ ofstream f_consts;
+ f_consts.open(f_consts_name.c_str());
+
+ // Print header
+ f_consts << autogen_comment() << dart_library(file_name) << endl;
+
+ export_class_to_library(file_name, class_name);
+ indent(f_consts) << "class " << class_name;
+ scope_up(f_consts);
+
+ vector<t_const*>::iterator c_iter;
+ for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) {
+ print_const_value(f_consts,
+ (*c_iter)->get_name(),
+ (*c_iter)->get_type(),
+ (*c_iter)->get_value(),
+ false);
+ f_consts << endl;
+ }
+
+ scope_down(f_consts);
+
+ f_consts.close();
+}
+
+void t_dart_generator::print_const_value(std::ofstream& out,
+ string name,
+ t_type* type,
+ t_const_value* value,
+ bool in_static,
+ bool defval) {
+ type = get_true_type(type);
+
+ indent(out);
+ if (!defval) {
+ out << (in_static ? "var " : "static final ");
+ }
+ if (type->is_base_type()) {
+ if (!defval) {
+ out << type_name(type) << " ";
+ }
+ string v2 = render_const_value(out, name, type, value);
+ out << name;
+ out << " = " << v2 << ";" << endl << endl;
+ } else if (type->is_enum()) {
+ if (!defval) {
+ out << type_name(type) << " ";
+ }
+ out << name;
+ out << " = " << value->get_integer() << ";" << endl << endl;
+ } else if (type->is_struct() || type->is_xception()) {
+ const vector<t_field*>& fields = ((t_struct*)type)->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ const map<t_const_value*, t_const_value*>& val = value->get_map();
+ map<t_const_value*, t_const_value*>::const_iterator v_iter;
+ out << type_name(type) << " " << name << " = new " << type_name(type) << "();"
+ << endl;
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+ t_type* field_type = NULL;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if ((*f_iter)->get_name() == v_iter->first->get_string()) {
+ field_type = (*f_iter)->get_type();
+ }
+ }
+ if (field_type == NULL) {
+ throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string();
+ }
+ string val = render_const_value(out, name, field_type, v_iter->second);
+ indent(out) << name << ".";
+ out << v_iter->first->get_string() << " = " << val << ";" << endl;
+ }
+ out << endl;
+ } else if (type->is_map()) {
+ if (!defval) {
+ out << type_name(type) << " ";
+ }
+ out << name << " =";
+ scope_up(out);
+
+ t_type* ktype = ((t_map*)type)->get_key_type();
+ t_type* vtype = ((t_map*)type)->get_val_type();
+ const map<t_const_value*, t_const_value*>& val = value->get_map();
+ map<t_const_value*, t_const_value*>::const_iterator v_iter;
+
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+ string key = render_const_value(out, name, ktype, v_iter->first);
+ string val = render_const_value(out, name, vtype, v_iter->second);
+ indent(out) << key << ": " << val << "," << endl;
+ }
+ scope_down(out, ";" + endl);
+
+ out << endl;
+ } else if (type->is_list() || type->is_set()) {
+ if (!defval) {
+ out << type_name(type) << " ";
+ }
+ out << name << " = ";
+ t_type* etype;
+ if (type->is_list()) {
+ out << "[" << endl;
+ etype = ((t_list*)type)->get_elem_type();
+ } else {
+ out << "new " << type_name(type) << ".from([" << endl;
+ etype = ((t_set*)type)->get_elem_type();
+ }
+ const vector<t_const_value*>& val = value->get_list();
+ vector<t_const_value*>::const_iterator v_iter;
+
+ indent_up();
+ for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+ string val = render_const_value(out, name, etype, *v_iter);
+ indent(out) << val << "," << endl;
+ }
+ indent_down();
+
+ if (type->is_list()) {
+ indent(out) << "];" << endl;
+ } else {
+ indent(out) << "]);" << endl;
+ }
+
+ } else {
+ throw "compiler error: no const of type " + type->get_name();
+ }
+}
+
+string t_dart_generator::render_const_value(ofstream& out,
+ string name,
+ t_type* type,
+ t_const_value* value) {
+ (void)name;
+ type = get_true_type(type);
+ std::ostringstream render;
+
+ if (type->is_base_type()) {
+ t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+ switch (tbase) {
+ case t_base_type::TYPE_STRING:
+ render << "'" << get_escaped_string(value) << "'";
+ break;
+ case t_base_type::TYPE_BOOL:
+ render << ((value->get_integer() > 0) ? "true" : "false");
+ break;
+ case t_base_type::TYPE_BYTE:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ case t_base_type::TYPE_I64:
+ render << value->get_integer();
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ if (value->get_type() == t_const_value::CV_INTEGER) {
+ render << value->get_integer();
+ } else {
+ render << value->get_double();
+ }
+ break;
+ default:
+ throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase);
+ }
+ } else if (type->is_enum()) {
+ render << value->get_integer();
+ } else {
+ string t = tmp("tmp");
+ print_const_value(out, t, type, value, true);
+ out << endl;
+ render << t;
+ }
+
+ return render.str();
+}
+
+/**
+ * Generates a struct definition for a thrift data type. This is a class
+ * with data members, read(), write(), and an inner Isset class.
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_struct(t_struct* tstruct) {
+ generate_dart_struct(tstruct, false);
+}
+
+/**
+ * Exceptions are structs, but they inherit from Exception
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_xception(t_struct* txception) {
+ generate_dart_struct(txception, true);
+}
+
+/**
+ * Dart struct definition.
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_struct(t_struct* tstruct, bool is_exception) {
+ string file_name = get_file_name(tstruct->get_name());
+ string f_struct_name = src_dir_ + "/" + file_name + ".dart";
+ ofstream f_struct;
+ f_struct.open(f_struct_name.c_str());
+
+ f_struct << autogen_comment() << dart_library(file_name) << endl;
+
+ string imports;
+
+ f_struct << dart_thrift_imports() << endl;
+
+ generate_dart_struct_definition(f_struct, tstruct, is_exception, false, file_name);
+
+ f_struct.close();
+}
+
+/**
+ * Dart struct definition. This has various parameters, as it could be
+ * generated standalone or inside another class as a helper. If it
+ * is a helper than it is a static class.
+ *
+ * @param tstruct The struct definition
+ * @param is_exception Is this an exception?
+ * @param in_class If inside a class, needs to be static class
+ * @param is_result If this is a result it needs a different writer
+ */
+void t_dart_generator::generate_dart_struct_definition(ofstream& out,
+ t_struct* tstruct,
+ bool is_exception,
+ bool is_result,
+ string export_file_name) {
+ generate_dart_doc(out, tstruct);
+
+ string class_name = tstruct->get_name();
+ if (!export_file_name.empty()) {
+ export_class_to_library(export_file_name, class_name);
+ }
+ indent(out) << "class " << class_name << " ";
+
+ if (is_exception) {
+ out << "extends Error ";
+ }
+ out << "implements TBase";
+ scope_up(out);
+
+ indent(out) << "static final TStruct _STRUCT_DESC = new TStruct(\"" << class_name
+ << "\");" << endl;
+
+ // Members are public for -dart, private for -dartbean
+ const vector<t_field*>& members = tstruct->get_members();
+ vector<t_field*>::const_iterator m_iter;
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+ indent(out) << "static final TField _" << constant_name((*m_iter)->get_name())
+ << "_FIELD_DESC = new TField(\"" << (*m_iter)->get_name() << "\", "
+ << type_to_enum((*m_iter)->get_type()) << ", " << (*m_iter)->get_key() << ");"
+ << endl;
+ }
+
+ out << endl;
+
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+ generate_dart_doc(out, *m_iter);
+ indent(out) << type_name((*m_iter)->get_type()) + " _"
+ << get_field_name((*m_iter)->get_name()) << ";" << endl;
+
+ indent(out) << "static const int " << upcase_string((*m_iter)->get_name())
+ << " = " << (*m_iter)->get_key() << ";" << endl;
+ }
+
+ out << endl;
+
+ // Inner Isset class
+ if (members.size() > 0) {
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+ if (!type_can_be_null((*m_iter)->get_type())) {
+ string field_name = get_field_name((*m_iter)->get_name());
+ indent(out) << "bool __isset_" << field_name << " = false;" << endl;
+ }
+ }
+ }
+
+ out << endl;
+
+ // Default constructor
+ indent(out) << tstruct->get_name() << "()";
+ scope_up(out);
+ for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+ t_type* t = get_true_type((*m_iter)->get_type());
+ if ((*m_iter)->get_value() != NULL) {
+ print_const_value(out,
+ "this." + (*m_iter)->get_name(),
+ t,
+ (*m_iter)->get_value(),
+ true,
+ true);
+ }
+ }
+ scope_down(out);
+ out << endl;
+
+ generate_dart_bean_boilerplate(out, tstruct);
+ generate_generic_field_getters(out, tstruct);
+ generate_generic_field_setters(out, tstruct);
+ generate_generic_isset_method(out, tstruct);
+
+ generate_dart_struct_reader(out, tstruct);
+ if (is_result) {
+ generate_dart_struct_result_writer(out, tstruct);
+ } else {
+ generate_dart_struct_writer(out, tstruct);
+ }
+ generate_dart_struct_tostring(out, tstruct);
+ generate_dart_validator(out, tstruct);
+ scope_down(out);
+ out << endl;
+}
+
+/**
+ * Generates a function to read all the fields of the struct.
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_struct_reader(ofstream& out, t_struct* tstruct) {
+ indent(out) << "read(TProtocol iprot)";
+ scope_up(out);
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ // Declare stack tmp variables and read struct header
+ indent(out) << "TField field;" << endl;
+ indent(out) << "iprot.readStructBegin();" << endl;
+
+ // Loop over reading in fields
+ indent(out) << "while (true)";
+ scope_up(out);
+
+ // Read beginning field marker
+ indent(out) << "field = iprot.readFieldBegin();" << endl;
+
+ // Check for field STOP marker and break
+ indent(out) << "if (field.type == TType.STOP)";
+ scope_up(out);
+ indent(out) << "break;" << endl;
+ scope_down(out);
+
+ // Switch statement on the field we are reading
+ indent(out) << "switch (field.id)";
+ scope_up(out);
+
+ // Generate deserialization code for known cases
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ indent(out) << "case " << upcase_string((*f_iter)->get_name()) << ":" << endl;
+ indent_up();
+
+ indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ")";
+ scope_up(out);
+
+ generate_deserialize_field(out, *f_iter, "this.");
+ generate_isset_set(out, *f_iter);
+
+ scope_down(out, " else");
+ scope_up(out);
+ indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl;
+ scope_down(out);
+
+ indent(out) << "break;" << endl;
+ indent_down();
+ }
+
+ // In the default case we skip the field
+ indent(out) << "default:" << endl;
+ indent_up();
+ indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl;
+ indent(out) << "break;" << endl;
+ indent_down();
+
+ scope_down(out);
+
+ // Read field end marker
+ indent(out) << "iprot.readFieldEnd();" << endl;
+
+ scope_down(out);
+
+ indent(out) << "iprot.readStructEnd();" << endl2;
+
+ // in non-beans style, check for required fields of primitive type
+ // (which can be checked here but not in the general validate method)
+ indent(out) << "// check for required fields of primitive type, which can't be "
+ "checked in the validate method" << endl;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) {
+ string field_name = get_field_name((*f_iter)->get_name());
+ indent(out) << "if (!__isset_" << field_name << ")";
+ scope_up(out);
+ indent(out) << " throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '"
+ << field_name
+ << "' was not found in serialized data! Struct: \" + toString());" << endl;
+ scope_down(out, endl2);
+ }
+ }
+
+ // performs various checks (e.g. check that all required fields are set)
+ indent(out) << "validate();" << endl;
+
+ scope_down(out, endl2);
+}
+
+// generates dart method to perform various checks
+// (e.g. check that all required fields are set)
+void t_dart_generator::generate_dart_validator(ofstream& out, t_struct* tstruct) {
+ indent(out) << "validate()";
+ scope_up(out);
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ indent(out) << "// check for required fields" << endl;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if ((*f_iter)->get_req() == t_field::T_REQUIRED) {
+ string field_name = get_field_name((*f_iter)->get_name());
+ if (type_can_be_null((*f_iter)->get_type())) {
+ indent(out) << "if (" << field_name << " == null)";
+ scope_up(out);
+ indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '"
+ << field_name << "' was not present! Struct: \" + toString());"
+ << endl;
+ scope_down(out);
+ } else {
+ indent(out) << "// alas, we cannot check '" << field_name
+ << "' because it's a primitive and you chose the non-beans generator." << endl;
+ }
+ }
+ }
+
+ // check that fields of type enum have valid values
+ indent(out) << "// check that fields of type enum have valid values" << endl;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = (*f_iter);
+ t_type* type = field->get_type();
+ // if field is an enum, check that its value is valid
+ if (type->is_enum()) {
+ string field_name = get_field_name(field->get_name());
+ indent(out) << "if (" << generate_isset_check(field) << " && !" << get_enum_class_name(type)
+ << ".VALID_VALUES.contains(" << field_name << "))";
+ scope_up(out);
+ indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"The field '"
+ << field_name << "' has been assigned the invalid value "
+ << "$" << field_name << "\");" << endl;
+ scope_down(out);
+ }
+ }
+
+ scope_down(out, endl2);
+}
+
+/**
+ * Generates a function to write all the fields of the struct
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_struct_writer(ofstream& out, t_struct* tstruct) {
+ out << indent() << "write(TProtocol oprot)";
+ scope_up(out);
+
+ string name = tstruct->get_name();
+ const vector<t_field*>& fields = tstruct->get_sorted_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ // performs various checks (e.g. check that all required fields are set)
+ indent(out) << "validate();" << endl2;
+
+ indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl;
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ string field_name = get_field_name((*f_iter)->get_name());
+ bool null_allowed = type_can_be_null((*f_iter)->get_type());
+ if (null_allowed) {
+ indent(out) << "if (this." << field_name << " != null)";
+ scope_up(out);
+ }
+
+ indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name())
+ << "_FIELD_DESC);" << endl;
+
+ // Write field contents
+ generate_serialize_field(out, *f_iter, "this.");
+
+ // Write field closer
+ indent(out) << "oprot.writeFieldEnd();" << endl;
+
+ if (null_allowed) {
+ scope_down(out);
+ }
+ }
+ // Write the struct map
+ indent(out) << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();"
+ << endl;
+
+ scope_down(out, endl2);
+}
+
+/**
+ * Generates a function to write all the fields of the struct,
+ * which is a function result. These fields are only written
+ * if they are set in the Isset array, and only one of them
+ * can be set at a time.
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_struct_result_writer(ofstream& out, t_struct* tstruct) {
+ indent(out) << "write(TProtocol oprot)";
+ scope_up(out);
+
+ string name = tstruct->get_name();
+ const vector<t_field*>& fields = tstruct->get_sorted_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl2;
+
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if (first) {
+ first = false;
+ indent(out) << "if ";
+ } else {
+ out << " else if ";
+ }
+
+ out << "(this." << generate_isset_check(*f_iter) << ")";
+ scope_up(out);
+
+ indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name())
+ << "_FIELD_DESC);" << endl;
+
+ // Write field contents
+ generate_serialize_field(out, *f_iter, "this.");
+
+ // Write field closer
+ indent(out) << "oprot.writeFieldEnd();" << endl;
+
+ scope_down(out, "");
+ }
+ out << endl;
+
+ // Write the struct map
+ indent(out) << "oprot.writeFieldStop();" << endl << indent()
+ << "oprot.writeStructEnd();" << endl;
+
+ scope_down(out, endl2);
+}
+
+void t_dart_generator::generate_generic_field_getters(std::ofstream& out,
+ t_struct* tstruct) {
+ // create the getter
+ indent(out) << "getFieldValue(int fieldID)";
+ scope_up(out);
+
+ indent(out) << "switch (fieldID)";
+ scope_up(out);
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = *f_iter;
+ std::string field_name = get_field_name(field->get_name());
+
+ indent(out) << "case " << upcase_string(field_name) << ":" << endl;
+ indent_up();
+ indent(out) << "return this." << field_name << ";" << endl;
+ indent_down();
+ }
+
+ indent(out) << "default:" << endl;
+ indent_up();
+ indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl;
+ indent_down();
+
+ scope_down(out); // switch
+ scope_down(out, endl2); // method
+}
+
+void t_dart_generator::generate_generic_field_setters(std::ofstream& out,
+ t_struct* tstruct) {
+
+ // create the setter
+ indent(out) << "setFieldValue(int fieldID, Object value)";
+ scope_up(out);
+
+ indent(out) << "switch (fieldID)";
+ scope_up(out);
+
+ // build up the bodies of both the getter and setter at once
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = *f_iter;
+ std::string field_name = get_field_name(field->get_name());
+
+ indent(out) << "case " << upcase_string(field_name) << ":" << endl;
+ indent_up();
+
+ indent(out) << "if (value == null)";
+ scope_up(out);
+ indent(out) << "unset" << get_cap_name(field_name) << "();" << endl;
+
+ scope_down(out, " else");
+ scope_up(out);
+ indent(out) << "this." << field_name << " = value;" << endl;
+ scope_down(out);
+
+ indent(out) << "break;" << endl;
+
+ indent_down();
+ out << endl;
+ }
+
+ indent(out) << "default:" << endl;
+ indent_up();
+ indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl;
+ indent_down();
+
+ scope_down(out); // switch
+ scope_down(out, endl2); // method
+}
+
+// Creates a generic isSet method that takes the field number as argument
+void t_dart_generator::generate_generic_isset_method(std::ofstream& out, t_struct* tstruct) {
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ // create the isSet method
+ indent(out) << "// Returns true if field corresponding to fieldID is set (has been assigned a "
+ "value) and false otherwise" << endl;
+ indent(out) << "bool isSet(int fieldID)";
+ scope_up(out);
+
+ indent(out) << "switch (fieldID)";
+ scope_up(out);
+
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = *f_iter;
+ indent(out) << "case " << upcase_string(field->get_name()) << ":" << endl;
+ indent_up();
+ indent(out) << "return " << generate_isset_check(field) << ";" << endl;
+ indent_down();
+ }
+
+ indent(out) << "default:" << endl;
+ indent_up();
+ indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl;
+ indent_down();
+
+ scope_down(out); // switch
+ scope_down(out, endl2); // method
+}
+
+/**
+ * Generates a set of Dart Bean boilerplate functions (setters, getters, etc.)
+ * for the given struct.
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_bean_boilerplate(ofstream& out,
+ t_struct* tstruct) {
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ t_field* field = *f_iter;
+ t_type* type = get_true_type(field->get_type());
+ std::string field_name = get_field_name(field->get_name());
+ std::string cap_name = get_cap_name(field_name);
+
+ indent(out) << "// " << field_name << endl;
+
+ // Simple getter
+ generate_dart_doc(out, field);
+ indent(out) << type_name(type) << " get " << field_name << " => this._" << field_name << ";" << endl2;
+
+ // Simple setter
+ generate_dart_doc(out, field);
+ indent(out) << "set " << field_name << "(" << type_name(type) << " " << field_name << ")";
+ scope_up(out);
+ indent(out) << "this._" << field_name << " = " << field_name << ";" << endl;
+ generate_isset_set(out, field);
+ scope_down(out, endl2);
+
+ // isSet method
+ indent(out) << "bool is" << get_cap_name("set") << cap_name << "()";
+ if (type_can_be_null(type)) {
+ out << " => this." << field_name << " != null;" << endl2;
+ } else {
+ out << " => this.__isset_" << field_name << ";" << endl2;
+ }
+
+ // Unsetter
+ indent(out) << "unset" << cap_name << "()";
+ scope_up(out);
+ if (type_can_be_null(type)) {
+ indent(out) << "this." << field_name << " = null;" << endl;
+ } else {
+ indent(out) << "this.__isset_" << field_name << " = false;" << endl;
+ }
+ scope_down(out, endl2);
+ }
+}
+
+/**
+ * Generates a toString() method for the given struct
+ *
+ * @param tstruct The struct definition
+ */
+void t_dart_generator::generate_dart_struct_tostring(ofstream& out,
+ t_struct* tstruct) {
+ indent(out) << "String toString()";
+ scope_up(out);
+
+ indent(out) << "StringBuffer ret = new StringBuffer(\""
+ << tstruct->get_name() << "(\");" << endl2;
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL;
+ if (could_be_unset) {
+ indent(out) << "if (" << generate_isset_check(*f_iter) << ")";
+ scope_up(out);
+ }
+
+ t_field* field = (*f_iter);
+ std::string field_name = get_field_name(field->get_name());
+
+ if (!first) {
+ indent(out) << "ret.write(\", \");" << endl;
+ }
+ indent(out) << "ret.write(\"" << field_name << ":\");" << endl;
+ bool can_be_null = type_can_be_null(field->get_type());
+ if (can_be_null) {
+ indent(out) << "if (this." << field_name << " == null)";
+ scope_up(out);
+ indent(out) << "ret.write(\"null\");" << endl;
+ scope_down(out, " else");
+ scope_up(out);
+ }
+
+ if (field->get_type()->is_base_type() && ((t_base_type*)(field->get_type()))->is_binary()) {
+ indent(out) << "ret.write(\"BINARY\");" << endl;
+ } else if (field->get_type()->is_enum()) {
+ indent(out) << "String " << field_name << "_name = "
+ << get_enum_class_name(field->get_type())
+ << ".VALUES_TO_NAMES[this." << field_name << "];" << endl;
+ indent(out) << "if (" << field_name << "_name != null)";
+ scope_up(out);
+ indent(out) << "ret.write(" << field_name << "_name);" << endl;
+ indent(out) << "ret.write(\" (\");" << endl;
+ scope_down(out);
+ indent(out) << "ret.write(this." << field_name << ");" << endl;
+ indent(out) << "if (" << field_name << "_name != null)";
+ scope_up(out);
+ indent(out) << "ret.write(\")\");" << endl;
+ scope_down(out);
+ } else {
+ indent(out) << "ret.write(this." << field_name << ");" << endl;
+ }
+
+ if (can_be_null) {
+ scope_down(out);
+ }
+ if (could_be_unset) {
+ scope_down(out);
+ }
+
+ out << endl;
+ first = false;
+ }
+
+ indent(out) << "ret.write(\")\");" << endl2;
+
+ indent(out) << "return ret.toString();" << endl;
+
+ scope_down(out, endl2);
+}
+
+/**
+ * Returns a string with the dart representation of the given thrift type
+ * (e.g. for the type struct it returns "TType.STRUCT")
+ */
+std::string t_dart_generator::get_dart_type_string(t_type* type) {
+ if (type->is_list()) {
+ return "TType.LIST";
+ } else if (type->is_map()) {
+ return "TType.MAP";
+ } else if (type->is_set()) {
+ return "TType.SET";
+ } else if (type->is_struct() || type->is_xception()) {
+ return "TType.STRUCT";
+ } else if (type->is_enum()) {
+ return "TType.I32";
+ } else if (type->is_typedef()) {
+ return get_dart_type_string(((t_typedef*)type)->get_type());
+ } else if (type->is_base_type()) {
+ switch (((t_base_type*)type)->get_base()) {
+ case t_base_type::TYPE_VOID:
+ return "TType.VOID";
+ break;
+ case t_base_type::TYPE_STRING:
+ return "TType.STRING";
+ break;
+ case t_base_type::TYPE_BOOL:
+ return "TType.BOOL";
+ break;
+ case t_base_type::TYPE_BYTE:
+ return "TType.BYTE";
+ break;
+ case t_base_type::TYPE_I16:
+ return "TType.I16";
+ break;
+ case t_base_type::TYPE_I32:
+ return "TType.I32";
+ break;
+ case t_base_type::TYPE_I64:
+ return "TType.I64";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ return "TType.DOUBLE";
+ break;
+ default:
+ throw std::runtime_error("Unknown thrift type \"" + type->get_name()
+ + "\" passed to t_dart_generator::get_dart_type_string!");
+ break; // This should never happen!
+ }
+ } else {
+ throw std::runtime_error(
+ "Unknown thrift type \"" + type->get_name()
+ + "\" passed to t_dart_generator::get_dart_type_string!"); // This should never happen!
+ }
+}
+
+void t_dart_generator::generate_service(t_service* tservice) {
+ string file_name = get_file_name(service_name_);
+ string f_service_name = src_dir_ + "/" + file_name + ".dart";
+ f_service_.open(f_service_name.c_str());
+
+ f_service_ << autogen_comment() << dart_library(file_name) << endl;
+ f_service_ << service_imports() << dart_thrift_imports() << endl;
+ f_service_ << endl;
+
+ generate_service_interface(tservice);
+ generate_service_client(tservice);
+ generate_service_server(tservice);
+ generate_service_helpers(tservice);
+
+ f_service_.close();
+}
+
+/**
+ * Generates a service interface definition.
+ *
+ * @param tservice The service to generate a header definition for
+ */
+void t_dart_generator::generate_service_interface(t_service* tservice) {
+ string extends_iface = "";
+ if (tservice->get_extends() != NULL) {
+ extends_iface = " extends " + tservice->get_extends()->get_name();
+ }
+
+ generate_dart_doc(f_service_, tservice);
+
+ string class_name = service_name_;
+ export_class_to_library(get_file_name(service_name_), class_name);
+ indent(f_service_) << "abstract class " << class_name << extends_iface;
+ scope_up(f_service_);
+
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ f_service_ << endl;
+ generate_dart_doc(f_service_, *f_iter);
+ indent(f_service_) << function_signature(*f_iter) << ";" << endl;
+ }
+
+ scope_down(f_service_, endl2);
+}
+
+/**
+ * Generates structs for all the service args and return types
+ *
+ * @param tservice The service
+ */
+void t_dart_generator::generate_service_helpers(t_service* tservice) {
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ t_struct* ts = (*f_iter)->get_arglist();
+ generate_dart_struct_definition(f_service_, ts, false, false);
+ generate_function_helpers(*f_iter);
+ }
+}
+
+/**
+ * Generates a service client definition.
+ *
+ * @param tservice The service to generate a server for.
+ */
+void t_dart_generator::generate_service_client(t_service* tservice) {
+ string extends = "";
+ string extends_client = "";
+ if (tservice->get_extends() != NULL) {
+ extends = tservice->get_extends()->get_name();
+ extends_client = " extends " + extends + "Client";
+ }
+
+ string class_name = service_name_ + "Client";
+ export_class_to_library(get_file_name(service_name_), class_name);
+ indent(f_service_) << "class " << class_name << extends_client
+ << " implements " << service_name_;
+ scope_up(f_service_);
+ f_service_ << endl;
+
+ indent(f_service_) << class_name << "(TProtocol iprot, [TProtocol oprot = null])";
+
+ if (!extends.empty()) {
+ indent_up();
+ f_service_ << endl;
+ indent(f_service_) << ": super(iprot, oprot);" << endl;
+ indent_down();
+ } else {
+ scope_up(f_service_);
+ indent(f_service_) << "_iprot = iprot;" << endl;
+ indent(f_service_) << "_oprot = (oprot == null) ? iprot : oprot;" << endl;
+ scope_down(f_service_);
+ }
+ f_service_ << endl;
+
+ if (extends.empty()) {
+ indent(f_service_) << "TProtocol _iprot;" << endl2;
+ indent(f_service_) << "TProtocol get iprot => _iprot;" << endl2;
+ indent(f_service_) << "TProtocol _oprot;" << endl2;
+ indent(f_service_) << "TProtocol get oprot => _oprot;" << endl2;
+ indent(f_service_) << "int _seqid = 0;" << endl2;
+ indent(f_service_) << "int get seqid => _seqid;" << endl2;
+ indent(f_service_) << "int nextSeqid() => ++_seqid;" << endl2;
+ }
+
+ // Generate client method implementations
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::const_iterator f_iter;
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ string funname = (*f_iter)->get_name();
+
+ // Open function
+ indent(f_service_) << function_signature(*f_iter) << " async";
+ scope_up(f_service_);
+
+ // Get the struct of function call params
+ t_struct* arg_struct = (*f_iter)->get_arglist();
+
+ string argsname = get_args_class_name((*f_iter)->get_name());
+ vector<t_field*>::const_iterator fld_iter;
+ const vector<t_field*>& fields = arg_struct->get_members();
+
+ // Serialize the request
+ indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << funname << "\", "
+ << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL")
+ << ", nextSeqid()));" << endl;
+ indent(f_service_) << argsname << " args = new " << argsname << "();" << endl;
+
+ for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
+ indent(f_service_) << "args." << (*fld_iter)->get_name() << " = "
+ << (*fld_iter)->get_name() << ";" << endl;
+ }
+
+ indent(f_service_) << "args.write(oprot);" << endl;
+ indent(f_service_) << "oprot.writeMessageEnd();" << endl2;
+
+ indent(f_service_) << "await oprot.transport.flush();" << endl2;
+
+ if (!(*f_iter)->is_oneway()) {
+ indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl;
+ indent(f_service_) << "if (msg.type == TMessageType.EXCEPTION)";
+ scope_up(f_service_);
+ indent(f_service_) << "TApplicationError error = TApplicationError.read(iprot);" << endl;
+ indent(f_service_) << "iprot.readMessageEnd();" << endl;
+ indent(f_service_) << "throw error;" << endl;
+ scope_down(f_service_, endl2);
+
+ string result_class = get_result_class_name((*f_iter)->get_name());
+ indent(f_service_) << result_class << " result = new " << result_class << "();" << endl;
+ indent(f_service_) << "result.read(iprot);" << endl;
+ indent(f_service_) << "iprot.readMessageEnd();" << endl;
+
+ // Careful, only return _result if not a void function
+ if (!(*f_iter)->get_returntype()->is_void()) {
+ indent(f_service_) << "if (result." << generate_isset_check("success") << ")";
+ scope_up(f_service_);
+ indent(f_service_) << "return result.success;" << endl;
+ scope_down(f_service_, endl2);
+ }
+
+ t_struct* xs = (*f_iter)->get_xceptions();
+ const std::vector<t_field*>& xceptions = xs->get_members();
+ vector<t_field*>::const_iterator x_iter;
+ for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
+ string result_field_name = get_field_name((*x_iter)->get_name());
+ indent(f_service_) << "if (result." << result_field_name << " != null)";
+ scope_up(f_service_);
+ indent(f_service_) << "throw result." << result_field_name << ";" << endl;
+ scope_down(f_service_);
+ }
+
+ // If you get here it's an exception, unless a void function
+ if ((*f_iter)->get_returntype()->is_void()) {
+ indent(f_service_) << "return;" << endl;
+ } else {
+ indent(f_service_) << "throw new TApplicationError(TApplicationErrorType.MISSING_RESULT, \""
+ << (*f_iter)->get_name() << " failed: unknown result\");" << endl;
+ }
+ }
+
+ scope_down(f_service_, endl2);
+ }
+
+ scope_down(f_service_, endl2);
+}
+
+/**
+ * Generates a service server definition.
+ *
+ * @param tservice The service to generate a server for.
+ */
+void t_dart_generator::generate_service_server(t_service* tservice) {
+ // Generate the dispatch methods
+ vector<t_function*> functions = tservice->get_functions();
+ vector<t_function*>::iterator f_iter;
+
+ // typedef
+ indent(f_service_) << "typedef void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" << endl2;
+
+ // Extends stuff
+ string extends = "";
+ string extends_processor = "";
+ if (tservice->get_extends() != NULL) {
+ extends = type_name(tservice->get_extends());
+ extends_processor = " extends " + extends + "Processor";
+ }
+
+ // Generate the header portion
+ string class_name = service_name_ + "Processor";
+ export_class_to_library(get_file_name(service_name_), class_name);
+ indent(f_service_) << "class " << class_name << extends_processor << " implements TProcessor";
+ scope_up(f_service_);
+
+ indent(f_service_) << class_name << "(" << service_name_ << " iface)";
+ if (!extends.empty()) {
+ indent_up();
+ f_service_ << endl;
+ indent(f_service_) << ": super(iface)";
+ indent_down();
+ }
+ scope_up(f_service_);
+
+ if (extends.empty()) {
+ indent(f_service_) << "iface_ = iface;" << endl;
+ }
+
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ indent(f_service_) << "PROCESS_MAP[\"" << (*f_iter)->get_name()
+ << "\"] = " << (*f_iter)->get_name() << ";" << endl;
+ }
+ scope_down(f_service_, endl2);
+
+ indent(f_service_) << service_name_ << " iface_;" << endl;
+
+ if (extends.empty()) {
+ indent(f_service_) << "final Map<String, ProcessFunction> PROCESS_MAP = {};" << endl;
+ }
+
+ f_service_ << endl;
+
+ // Generate the server implementation
+ indent(f_service_) << "bool process(TProtocol iprot, TProtocol oprot)";
+ scope_up(f_service_);
+ indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl;
+ indent(f_service_) << "ProcessFunction fn = PROCESS_MAP[msg.name];" << endl;
+ indent(f_service_) << "if (fn == null)";
+ scope_up(f_service_);
+ indent(f_service_) << "TProtocolUtil.skip(iprot, TType.STRUCT);" << endl;
+ indent(f_service_) << "iprot.readMessageEnd();" << endl;
+ indent(f_service_) << "TApplicationError x = new TApplicationError(TApplicationErrorType.UNKNOWN_METHOD, "
+ "\"Invalid method name: '\"+msg.name+\"'\");" << endl;
+ indent(f_service_) << "oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" << endl;
+ indent(f_service_) << "x.write(oprot);" << endl;
+ indent(f_service_) << "oprot.writeMessageEnd();" << endl;
+ indent(f_service_) << "oprot.transport.flush();" << endl;
+ indent(f_service_) << "return true;" << endl;
+ scope_down(f_service_);
+ indent(f_service_) << "fn(msg.seqid, iprot, oprot);" << endl;
+ indent(f_service_) << "return true;" << endl;
+ scope_down(f_service_, endl2); // process function
+
+ // Generate the process subfunctions
+ for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+ generate_process_function(tservice, *f_iter);
+ }
+
+ scope_down(f_service_, endl2); // class
+}
+
+/**
+ * Generates a struct and helpers for a function.
+ *
+ * @param tfunction The function
+ */
+void t_dart_generator::generate_function_helpers(t_function* tfunction) {
+ if (tfunction->is_oneway()) {
+ return;
+ }
+
+ t_struct result(program_, get_result_class_name(tfunction->get_name()));
+ t_field success(tfunction->get_returntype(), "success", 0);
+ if (!tfunction->get_returntype()->is_void()) {
+ result.append(&success);
+ }
+
+ t_struct* xs = tfunction->get_xceptions();
+ const vector<t_field*>& fields = xs->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ result.append(*f_iter);
+ }
+
+ generate_dart_struct_definition(f_service_, &result, false, true);
+}
+
+/**
+ * Generates a process function definition.
+ *
+ * @param tfunction The function to write a dispatcher for
+ */
+void t_dart_generator::generate_process_function(t_service* tservice, t_function* tfunction) {
+ (void)tservice;
+
+ bool await_result = (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void());
+
+ indent(f_service_) << tfunction->get_name() << "(int seqid, TProtocol iprot, TProtocol oprot)";
+ if (await_result) {
+ f_service_ << " async";
+ }
+ scope_up(f_service_);
+
+ string argsname = get_args_class_name(tfunction->get_name());
+ string resultname = get_result_class_name(tfunction->get_name());
+
+ indent(f_service_) << argsname << " args = new " << argsname << "();" << endl;
+ indent(f_service_) << "args.read(iprot);" << endl;
+ indent(f_service_) << "iprot.readMessageEnd();" << endl;
+
+ t_struct* xs = tfunction->get_xceptions();
+ const std::vector<t_field*>& xceptions = xs->get_members();
+ vector<t_field*>::const_iterator x_iter;
+
+ if (!tfunction->is_oneway()) {
+ indent(f_service_) << resultname << " result = new " << resultname << "();" << endl;
+ }
+
+ if (!tfunction->is_oneway() && xceptions.size() > 0) {
+ indent(f_service_) << "try";
+ scope_up(f_service_);
+ }
+
+ // Generate the function call
+ t_struct* arg_struct = tfunction->get_arglist();
+ const std::vector<t_field*>& fields = arg_struct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+
+ f_service_ << indent();
+ if (await_result) {
+ f_service_ << "result.success = await ";
+ }
+ f_service_ << "iface_." << tfunction->get_name() << "(";
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if (first) {
+ first = false;
+ } else {
+ f_service_ << ", ";
+ }
+ f_service_ << "args." << get_field_name((*f_iter)->get_name());
+ }
+ f_service_ << ");" << endl;
+
+ if (!tfunction->is_oneway() && xceptions.size() > 0) {
+ for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
+ string result_field_name = get_field_name((*x_iter)->get_name());
+ scope_down(f_service_, "");
+ f_service_ << " on " << type_name((*x_iter)->get_type())
+ << " catch(" << result_field_name << ")";
+ scope_up(f_service_);
+ if (!tfunction->is_oneway()) {
+ indent(f_service_) << "result." << result_field_name << " = "
+ << result_field_name << ";" << endl;
+ }
+ }
+ scope_down(f_service_, " ");
+ f_service_ << "catch (th)";
+ scope_up(f_service_);
+ indent(f_service_) << "// Internal error" << endl;
+ indent(f_service_) << "TApplicationError x = new "
+ "TApplicationError(TApplicationErrorType.INTERNAL_ERROR, \"Internal error processing "
+ << tfunction->get_name() << "\");" << endl;
+ indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name()
+ << "\", TMessageType.EXCEPTION, seqid));" << endl;
+ indent(f_service_) << "x.write(oprot);" << endl;
+ indent(f_service_) << "oprot.writeMessageEnd();" << endl;
+ indent(f_service_) << "oprot.transport.flush();" << endl;
+ indent(f_service_) << "return;" << endl;
+ scope_down(f_service_);
+ }
+
+ if (tfunction->is_oneway()) {
+ indent(f_service_) << "return;" << endl;
+ } else {
+ indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name()
+ << "\", TMessageType.REPLY, seqid));" << endl;
+ indent(f_service_) << "result.write(oprot);" << endl;
+ indent(f_service_) << "oprot.writeMessageEnd();" << endl;
+ indent(f_service_) << "oprot.transport.flush();" << endl;
+ }
+
+ scope_down(f_service_, endl2);
+}
+
+/**
+ * Deserializes a field of any type.
+ *
+ * @param tfield The field
+ * @param prefix The variable name or container for this field
+ */
+void t_dart_generator::generate_deserialize_field(ofstream& out, t_field* tfield, string prefix) {
+ t_type* type = get_true_type(tfield->get_type());
+ string field_name = get_field_name(tfield->get_name());
+
+ if (type->is_void()) {
+ throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + field_name;
+ }
+
+ string name = prefix + field_name;
+
+ if (type->is_struct() || type->is_xception()) {
+ generate_deserialize_struct(out, (t_struct*)type, name);
+ } else if (type->is_container()) {
+ generate_deserialize_container(out, type, name);
+ } else if (type->is_base_type() || type->is_enum()) {
+
+ indent(out) << name << " = iprot.";
+
+ 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 "compiler error: cannot serialize void field in a struct: " + name;
+ break;
+ case t_base_type::TYPE_STRING:
+ if (((t_base_type*)type)->is_binary()) {
+ out << "readBinary();";
+ } else {
+ out << "readString();";
+ }
+ break;
+ case t_base_type::TYPE_BOOL:
+ out << "readBool();";
+ break;
+ case t_base_type::TYPE_BYTE:
+ out << "readByte();";
+ break;
+ case t_base_type::TYPE_I16:
+ out << "readI16();";
+ break;
+ case t_base_type::TYPE_I32:
+ out << "readI32();";
+ break;
+ case t_base_type::TYPE_I64:
+ out << "readI64();";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ out << "readDouble();";
+ break;
+ default:
+ throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase);
+ }
+ } else if (type->is_enum()) {
+ out << "readI32();";
+ }
+ out << endl;
+ } else {
+ printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n",
+ field_name.c_str(),
+ type_name(type).c_str());
+ }
+}
+
+/**
+ * Generates an unserializer for a struct, invokes read()
+ */
+void t_dart_generator::generate_deserialize_struct(ofstream& out, t_struct* tstruct, string prefix) {
+ indent(out) << prefix << " = new " << type_name(tstruct) << "();" << endl;
+ indent(out) << prefix << ".read(iprot);" << endl;
+}
+
+/**
+ * Deserializes a container by reading its size and then iterating
+ */
+void t_dart_generator::generate_deserialize_container(ofstream& out, t_type* ttype, string prefix) {
+ indent(out);
+ scope_up(out, "");
+
+ string obj;
+
+ if (ttype->is_map()) {
+ obj = tmp("_map");
+ } else if (ttype->is_set()) {
+ obj = tmp("_set");
+ } else if (ttype->is_list()) {
+ obj = tmp("_list");
+ }
+
+ // Declare variables, read header
+ if (ttype->is_map()) {
+ indent(out) << "TMap " << obj << " = iprot.readMapBegin();" << endl;
+ } else if (ttype->is_set()) {
+ indent(out) << "TSet " << obj << " = iprot.readSetBegin();" << endl;
+ } else if (ttype->is_list()) {
+ indent(out) << "TList " << obj << " = iprot.readListBegin();" << endl;
+ }
+
+ indent(out) << prefix << " = new " << type_name(ttype) << "();" << endl;
+
+ // For loop iterates over elements
+ string i = tmp("_i");
+ indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".length"
+ << "; "
+ << "++" << i << ")";
+ scope_up(out);
+
+ if (ttype->is_map()) {
+ generate_deserialize_map_element(out, (t_map*)ttype, prefix);
+ } else if (ttype->is_set()) {
+ generate_deserialize_set_element(out, (t_set*)ttype, prefix);
+ } else if (ttype->is_list()) {
+ generate_deserialize_list_element(out, (t_list*)ttype, prefix);
+ }
+
+ scope_down(out);
+
+ // Read container end
+ if (ttype->is_map()) {
+ indent(out) << "iprot.readMapEnd();" << endl;
+ } else if (ttype->is_set()) {
+ indent(out) << "iprot.readSetEnd();" << endl;
+ } else if (ttype->is_list()) {
+ indent(out) << "iprot.readListEnd();" << endl;
+ }
+
+ scope_down(out);
+}
+
+/**
+ * Generates code to deserialize a map
+ */
+void t_dart_generator::generate_deserialize_map_element(ofstream& out, t_map* tmap, string prefix) {
+ string key = tmp("_key");
+ string val = tmp("_val");
+ t_field fkey(tmap->get_key_type(), key);
+ t_field fval(tmap->get_val_type(), val);
+
+ indent(out) << declare_field(&fkey) << endl;
+ indent(out) << declare_field(&fval) << endl;
+
+ generate_deserialize_field(out, &fkey);
+ generate_deserialize_field(out, &fval);
+
+ indent(out) << prefix << "[" << key << "] = " << val << ";" << endl;
+}
+
+/**
+ * Deserializes a set element
+ */
+void t_dart_generator::generate_deserialize_set_element(ofstream& out, t_set* tset, string prefix) {
+ string elem = tmp("_elem");
+ t_field felem(tset->get_elem_type(), elem);
+
+ indent(out) << declare_field(&felem) << endl;
+
+ generate_deserialize_field(out, &felem);
+
+ indent(out) << prefix << ".add(" << elem << ");" << endl;
+}
+
+/**
+ * Deserializes a list element
+ */
+void t_dart_generator::generate_deserialize_list_element(ofstream& out,
+ t_list* tlist,
+ string prefix) {
+ string elem = tmp("_elem");
+ t_field felem(tlist->get_elem_type(), elem);
+
+ indent(out) << declare_field(&felem) << endl;
+
+ generate_deserialize_field(out, &felem);
+
+ indent(out) << prefix << ".add(" << elem << ");" << endl;
+}
+
+/**
+ * Serializes a field of any type.
+ *
+ * @param tfield The field to serialize
+ * @param prefix Name to prepend to field name
+ */
+void t_dart_generator::generate_serialize_field(ofstream& out, t_field* tfield, string prefix) {
+ t_type* type = get_true_type(tfield->get_type());
+ string field_name = get_field_name(tfield->get_name());
+
+ // Do nothing for void types
+ if (type->is_void()) {
+ throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + field_name;
+ }
+
+ if (type->is_struct() || type->is_xception()) {
+ generate_serialize_struct(out, (t_struct*)type, prefix + field_name);
+ } else if (type->is_container()) {
+ generate_serialize_container(out, type, prefix + field_name);
+ } else if (type->is_base_type() || type->is_enum()) {
+
+ string name = prefix + field_name;
+ indent(out) << "oprot.";
+
+ 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 "compiler error: cannot serialize void field in a struct: " + name;
+ break;
+ case t_base_type::TYPE_STRING:
+ if (((t_base_type*)type)->is_binary()) {
+ out << "writeBinary(" << name << ");";
+ } else {
+ out << "writeString(" << name << ");";
+ }
+ break;
+ case t_base_type::TYPE_BOOL:
+ out << "writeBool(" << name << ");";
+ break;
+ case t_base_type::TYPE_BYTE:
+ out << "writeByte(" << name << ");";
+ break;
+ case t_base_type::TYPE_I16:
+ out << "writeI16(" << name << ");";
+ break;
+ case t_base_type::TYPE_I32:
+ out << "writeI32(" << name << ");";
+ break;
+ case t_base_type::TYPE_I64:
+ out << "writeI64(" << name << ");";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ out << "writeDouble(" << name << ");";
+ break;
+ default:
+ throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase);
+ }
+ } else if (type->is_enum()) {
+ out << "writeI32(" << name << ");";
+ }
+ out << endl;
+ } else {
+ printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n",
+ prefix.c_str(),
+ field_name.c_str(),
+ type_name(type).c_str());
+ }
+}
+
+/**
+ * Serializes all the members of a struct.
+ *
+ * @param tstruct The struct to serialize
+ * @param prefix String prefix to attach to all fields
+ */
+void t_dart_generator::generate_serialize_struct(ofstream& out, t_struct* tstruct, string prefix) {
+ (void)tstruct;
+ indent(out) << prefix << ".write(oprot);" << endl;
+}
+
+/**
+ * Serializes a container by writing its size then the elements.
+ *
+ * @param ttype The type of container
+ * @param prefix String prefix for fields
+ */
+void t_dart_generator::generate_serialize_container(ofstream& out, t_type* ttype, string prefix) {
+ indent(out);
+ scope_up(out, "");
+
+ if (ttype->is_map()) {
+ string iter = tmp("_key");
+ indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type())
+ << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix << ".length));"
+ << endl;
+ } else if (ttype->is_set()) {
+ indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type())
+ << ", " << prefix << ".length));" << endl;
+ } else if (ttype->is_list()) {
+ indent(out) << "oprot.writeListBegin(new TList("
+ << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".length));"
+ << endl;
+ }
+
+ string iter = tmp("elem");
+ if (ttype->is_map()) {
+ indent(out) << "for (var " << iter << " in " << prefix << ".keys)";
+ } else if (ttype->is_set() || ttype->is_list()) {
+ indent(out) << "for (var " << iter << " in " << prefix << ")";
+ }
+
+ scope_up(out);
+
+ if (ttype->is_map()) {
+ generate_serialize_map_element(out, (t_map*)ttype, iter, prefix);
+ } else if (ttype->is_set()) {
+ generate_serialize_set_element(out, (t_set*)ttype, iter);
+ } else if (ttype->is_list()) {
+ generate_serialize_list_element(out, (t_list*)ttype, iter);
+ }
+
+ scope_down(out);
+
+ if (ttype->is_map()) {
+ indent(out) << "oprot.writeMapEnd();" << endl;
+ } else if (ttype->is_set()) {
+ indent(out) << "oprot.writeSetEnd();" << endl;
+ } else if (ttype->is_list()) {
+ indent(out) << "oprot.writeListEnd();" << endl;
+ }
+
+ scope_down(out);
+}
+
+/**
+ * Serializes the members of a map.
+ */
+void t_dart_generator::generate_serialize_map_element(ofstream& out,
+ t_map* tmap,
+ string iter,
+ string map) {
+ t_field kfield(tmap->get_key_type(), iter);
+ generate_serialize_field(out, &kfield, "");
+ t_field vfield(tmap->get_val_type(), map + "[" + iter + "]");
+ generate_serialize_field(out, &vfield, "");
+}
+
+/**
+ * Serializes the members of a set.
+ */
+void t_dart_generator::generate_serialize_set_element(ofstream& out, t_set* tset, string iter) {
+ t_field efield(tset->get_elem_type(), iter);
+ generate_serialize_field(out, &efield, "");
+}
+
+/**
+ * Serializes the members of a list.
+ */
+void t_dart_generator::generate_serialize_list_element(ofstream& out, t_list* tlist, string iter) {
+ t_field efield(tlist->get_elem_type(), iter);
+ generate_serialize_field(out, &efield, "");
+}
+
+/**
+ * Returns a Dart type name
+ *
+ * @param ttype The type
+ * @return Dart type name, i.e. Map<Key, Value>
+ */
+string t_dart_generator::type_name(t_type* ttype) {
+ ttype = get_true_type(ttype);
+
+ if (ttype->is_base_type()) {
+ return base_type_name((t_base_type*)ttype);
+ } else if (ttype->is_enum()) {
+ return "int";
+ } else if (ttype->is_map()) {
+ t_map* tmap = (t_map*)ttype;
+ return "Map<" + type_name(tmap->get_key_type()) + ", "
+ + type_name(tmap->get_val_type()) + ">";
+ } else if (ttype->is_set()) {
+ t_set* tset = (t_set*)ttype;
+ return "Set<" + type_name(tset->get_elem_type()) + ">";
+ } else if (ttype->is_list()) {
+ t_list* tlist = (t_list*)ttype;
+ return "List<" + type_name(tlist->get_elem_type()) + ">";
+ }
+
+ return ttype->get_name();
+}
+
+/**
+ * Returns the Dart type that corresponds to the thrift type.
+ *
+ * @param tbase The base type
+ */
+string t_dart_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 "void";
+ case t_base_type::TYPE_STRING:
+ if (type->is_binary()) {
+ return "Uint8List";
+ } else {
+ return "String";
+ }
+ case t_base_type::TYPE_BOOL:
+ return "bool";
+ case t_base_type::TYPE_BYTE:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ case t_base_type::TYPE_I64:
+ return "int";
+ case t_base_type::TYPE_DOUBLE:
+ return "double";
+ default:
+ throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase);
+ }
+}
+
+/**
+ * Declares a field, which may include initialization as necessary.
+ *
+ * @param ttype The type
+ */
+string t_dart_generator::declare_field(t_field* tfield, bool init) {
+ string field_name = get_field_name(tfield->get_name());
+ string result = type_name(tfield->get_type()) + " " + field_name;
+ if (init) {
+ t_type* ttype = get_true_type(tfield->get_type());
+ if (ttype->is_base_type() && tfield->get_value() != NULL) {
+ ofstream dummy;
+ result += " = " + render_const_value(dummy, field_name, ttype, tfield->get_value());
+ } else if (ttype->is_base_type()) {
+ t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base();
+ switch (tbase) {
+ case t_base_type::TYPE_VOID:
+ throw "NO T_VOID CONSTRUCT";
+ case t_base_type::TYPE_STRING:
+ result += " = null";
+ break;
+ case t_base_type::TYPE_BOOL:
+ result += " = false";
+ break;
+ case t_base_type::TYPE_BYTE:
+ case t_base_type::TYPE_I16:
+ case t_base_type::TYPE_I32:
+ case t_base_type::TYPE_I64:
+ result += " = 0";
+ break;
+ case t_base_type::TYPE_DOUBLE:
+ result += " = 0.0";
+ break;
+ }
+
+ } else if (ttype->is_enum()) {
+ result += " = 0";
+ } else if (ttype->is_container()) {
+ result += " = new " + type_name(ttype) + "()";
+ } else {
+ result += " = new " + type_name(ttype) + "()";
+ ;
+ }
+ }
+ return result + ";";
+}
+
+/**
+ * Renders a function signature of the form 'type name(args)'
+ *
+ * @param tfunction Function definition
+ * @return String of rendered function definition
+ */
+string t_dart_generator::function_signature(t_function* tfunction) {
+ std::string arguments = argument_list(tfunction->get_arglist());
+
+ std::string returntype;
+ if (tfunction->get_returntype()->is_void()) {
+ returntype = "Future";
+ } else {
+ returntype = "Future<" + type_name(tfunction->get_returntype()) + ">";
+ }
+
+ std::string result = returntype + " " + tfunction->get_name() + "(" + arguments + ")";
+ return result;
+}
+
+/**
+ * Renders a comma separated field list, with type names
+ */
+string t_dart_generator::argument_list(t_struct* tstruct) {
+ string result = "";
+
+ const vector<t_field*>& fields = tstruct->get_members();
+ vector<t_field*>::const_iterator f_iter;
+ bool first = true;
+ for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+ if (first) {
+ first = false;
+ } else {
+ result += ", ";
+ }
+ string field_name = get_field_name((*f_iter)->get_name());
+ result += type_name((*f_iter)->get_type()) + " " + field_name;
+ }
+ return result;
+}
+
+/**
+ * Converts the parse type to a C++ enum string for the given type.
+ */
+string t_dart_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 "TType.STRING";
+ case t_base_type::TYPE_BOOL:
+ return "TType.BOOL";
+ case t_base_type::TYPE_BYTE:
+ return "TType.BYTE";
+ case t_base_type::TYPE_I16:
+ return "TType.I16";
+ case t_base_type::TYPE_I32:
+ return "TType.I32";
+ case t_base_type::TYPE_I64:
+ return "TType.I64";
+ case t_base_type::TYPE_DOUBLE:
+ return "TType.DOUBLE";
+ }
+ } else if (type->is_enum()) {
+ return "TType.I32";
+ } else if (type->is_struct() || type->is_xception()) {
+ return "TType.STRUCT";
+ } else if (type->is_map()) {
+ return "TType.MAP";
+ } else if (type->is_set()) {
+ return "TType.SET";
+ } else if (type->is_list()) {
+ return "TType.LIST";
+ }
+
+ throw "INVALID TYPE IN type_to_enum: " + type->get_name();
+}
+
+std::string t_dart_generator::get_cap_name(std::string name) {
+ name[0] = toupper(name[0]);
+ return name;
+}
+
+std::string t_dart_generator::get_field_name(std::string name) {
+ name[0] = tolower(name[0]);
+ return name;
+}
+
+std::string t_dart_generator::get_args_class_name(std::string name) {
+ return name + "_args";
+}
+
+std::string t_dart_generator::get_result_class_name(std::string name) {
+ return name + "_result";
+}
+
+std::string t_dart_generator::get_file_name(std::string name) {
+ // e.g. change APIForFileIO to api_for_file_io
+
+ string ret;
+ const char* tmp = name.c_str();
+ bool is_prev_lc = true;
+ bool is_current_lc = tmp[0] == tolower(tmp[0]);
+ bool is_next_lc = false;
+
+ for (unsigned int i = 0; i < name.length(); i++) {
+ char lc = tolower(tmp[i]);
+
+ if (i == name.length() - 1) {
+ is_next_lc = false;
+ } else {
+ is_next_lc = (tmp[i+1] == tolower(tmp[i+1]));
+ }
+
+ if (i != 0 && !is_current_lc && (is_prev_lc || is_next_lc)) {
+ ret += "_";
+ }
+ ret += lc;
+
+ is_prev_lc = is_current_lc;
+ is_current_lc = is_next_lc;
+ }
+
+ return ret;
+}
+
+string t_dart_generator::constant_name(string name) {
+ string constant_name;
+
+ bool is_first = true;
+ bool was_previous_char_upper = false;
+ for (string::iterator iter = name.begin(); iter != name.end(); ++iter) {
+ string::value_type character = (*iter);
+
+ 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;
+}
+
+/**
+ * Emits a doc comment if the provided object has a doc in Thrift
+ */
+void t_dart_generator::generate_dart_doc(ofstream& out, t_doc* tdoc) {
+ if (tdoc->has_doc()) {
+ generate_docstring_comment(out, "", "/// ", tdoc->get_doc(), "");
+ }
+}
+
+/**
+ * Emits a doc comment if the provided function object has a doc in Thrift
+ */
+void t_dart_generator::generate_dart_doc(ofstream& out, t_function* tfunction) {
+ if (tfunction->has_doc()) {
+ stringstream ss;
+ ss << tfunction->get_doc();
+ const vector<t_field*>& fields = tfunction->get_arglist()->get_members();
+ vector<t_field*>::const_iterator p_iter;
+ for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) {
+ t_field* p = *p_iter;
+ string field_name = get_field_name(p->get_name());
+ ss << "\n@param " << field_name;
+ if (p->has_doc()) {
+ ss << " " << p->get_doc();
+ }
+ }
+ generate_docstring_comment(out, "", "/// ", ss.str(), "");
+ }
+}
+
+std::string t_dart_generator::generate_isset_check(t_field* field) {
+ string field_name = get_field_name(field->get_name());
+ return generate_isset_check(field_name);
+}
+
+std::string t_dart_generator::generate_isset_check(std::string field_name) {
+ return "is" + get_cap_name("set") + get_cap_name(field_name) + "()";
+}
+
+void t_dart_generator::generate_isset_set(ofstream& out, t_field* field) {
+ if (!type_can_be_null(field->get_type())) {
+ string field_name = get_field_name(field->get_name());
+ indent(out) << "this.__isset_" << field_name << " = true;" << endl;
+ }
+}
+
+std::string t_dart_generator::get_enum_class_name(t_type* type) {
+ return type->get_name();
+}
+
+THRIFT_REGISTER_GENERATOR(
+ dart,
+ "Dart",
+ " library_name=my_library Optional override for library name.\n"
+)
diff --git a/configure.ac b/configure.ac
index 880cc32..e6b3be0 100755
--- a/configure.ac
+++ b/configure.ac
@@ -122,6 +122,7 @@
with_perl="no"
with_php="no"
with_php_extension="no"
+ with_dart="no"
with_erlang="no"
with_go="no"
with_d="no"
@@ -325,6 +326,16 @@
AC_PATH_PROG([PHPUNIT], [phpunit])
AM_CONDITIONAL(HAVE_PHPUNIT, [test "x$PHPUNIT" != "x"])
+AX_THRIFT_LIB(dart, [DART], yes)
+if test "$with_dart" = "yes"; then
+ AC_PATH_PROG([DART], [dart])
+ AC_PATH_PROG([DARTPUB], [pub])
+ if test "x$DART" != "x" -a "x$DARTPUB" != "x"; then
+ have_dart="yes"
+ fi
+fi
+AM_CONDITIONAL(WITH_DART, [test "$have_dart" = "yes"])
+
AX_THRIFT_LIB(ruby, [Ruby], yes)
have_ruby=no
if test "$with_ruby" = "yes"; then
@@ -727,6 +738,7 @@
lib/perl/test/Makefile
lib/php/Makefile
lib/php/test/Makefile
+ lib/dart/Makefile
lib/py/Makefile
lib/rb/Makefile
lib/lua/Makefile
@@ -738,6 +750,7 @@
test/haxe/Makefile
test/hs/Makefile
test/php/Makefile
+ test/dart/Makefile
test/perl/Makefile
test/py/Makefile
test/py.twisted/Makefile
@@ -752,6 +765,7 @@
tutorial/java/Makefile
tutorial/js/Makefile
tutorial/nodejs/Makefile
+ tutorial/dart/Makefile
tutorial/py/Makefile
tutorial/py.twisted/Makefile
tutorial/py.tornado/Makefile
@@ -776,6 +790,8 @@
AC_SUBST([MAYBE_PERL])
if test "$have_php" = "yes" ; then MAYBE_PHP="php" ; else MAYBE_PHP="" ; fi
AC_SUBST([MAYBE_PHP])
+if test "$have_dart" = "yes" ; then MAYBE_DART="dart" ; else MAYBE_DART="" ; fi
+AC_SUBST([MAYBE_DART])
if test "$have_go" = "yes" ; then MAYBE_GO="go" ; else MAYBE_GO="" ; fi
AC_SUBST([MAYBE_GO])
if test "$have_nodejs" = "yes" ; then MAYBE_NODEJS="nodejs" ; else MAYBE_NODEJS="" ; fi
@@ -797,6 +813,7 @@
echo "Building Haskell Library ..... : $have_haskell"
echo "Building Perl Library ........ : $have_perl"
echo "Building PHP Library ......... : $have_php"
+echo "Building Dart Library ........ : $have_dart"
echo "Building Erlang Library ...... : $have_erlang"
echo "Building Go Library .......... : $have_go"
echo "Building D Library ........... : $have_d"
@@ -834,6 +851,12 @@
echo "PHP Library:"
echo " Using php-config .......... : $PHP_CONFIG"
fi
+if test "$have_dart" = "yes" ; then
+ echo
+ echo "Dart Library:"
+ echo " Using Dart ................ : $DART"
+ echo " Using Pub ................. : $DARTPUB"
+fi
if test "$have_ruby" = "yes" ; then
echo
echo "Ruby Library:"
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5066a00..44bb9ff 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -65,6 +65,10 @@
SUBDIRS += php
endif
+if WITH_DART
+SUBDIRS += dart
+endif
+
if WITH_GO
SUBDIRS += go
endif
@@ -88,6 +92,7 @@
as3 \
cocoa \
d \
+ dart \
delphi \
haxe \
javame \
diff --git a/lib/dart/LICENSE_HEADER b/lib/dart/LICENSE_HEADER
new file mode 100644
index 0000000..4eacb64
--- /dev/null
+++ b/lib/dart/LICENSE_HEADER
@@ -0,0 +1,16 @@
+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.
diff --git a/lib/dart/Makefile.am b/lib/dart/Makefile.am
new file mode 100644
index 0000000..6dfff40
--- /dev/null
+++ b/lib/dart/Makefile.am
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+all-local:
+ $(DARTPUB) get
+
+clean-local:
+ $(RM) -r .pub
+
+check-local: all
diff --git a/lib/dart/README.md b/lib/dart/README.md
new file mode 100644
index 0000000..2be168b
--- /dev/null
+++ b/lib/dart/README.md
@@ -0,0 +1,26 @@
+Thrift Dart Library
+
+License
+=======
+
+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.
+
+Using Thrift with Dart
+====================
+
+Dart 1.12.0 or newer is required
diff --git a/lib/dart/coding_standards.md b/lib/dart/coding_standards.md
new file mode 100644
index 0000000..62f6003
--- /dev/null
+++ b/lib/dart/coding_standards.md
@@ -0,0 +1,6 @@
+# Dart Coding Standards
+
+### Please follow:
+ * [Thrift General Coding Standards](/doc/coding_standards.md)
+ * [Use dartfmt](https://www.dartlang.org/tools/dartfmt/) and follow the
+ [Dart Style Guide](https://www.dartlang.org/articles/style-guide/)
diff --git a/lib/dart/lib/src/browser/t_web_socket.dart b/lib/dart/lib/src/browser/t_web_socket.dart
new file mode 100644
index 0000000..dfcee83
--- /dev/null
+++ b/lib/dart/lib/src/browser/t_web_socket.dart
@@ -0,0 +1,130 @@
+/// 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.
+
+library thrift.src.browser;
+
+import 'dart:async';
+import 'dart:html' show CloseEvent;
+import 'dart:html' show Event;
+import 'dart:html' show MessageEvent;
+import 'dart:html' show WebSocket;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [WebSocket] from dart:html
+class TWebSocket implements TSocket {
+ final Uri url;
+
+ final StreamController<TSocketState> _onStateController;
+ Stream<TSocketState> get onState => _onStateController.stream;
+
+ final StreamController<Object> _onErrorController;
+ Stream<Object> get onError => _onErrorController.stream;
+
+ final StreamController<Uint8List> _onMessageController;
+ Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+ final List<Uint8List> _requests = [];
+
+ TWebSocket(this.url)
+ : _onStateController = new StreamController.broadcast(),
+ _onErrorController = new StreamController.broadcast(),
+ _onMessageController = new StreamController.broadcast() {
+ if (url == null || !url.hasAuthority || !url.hasPort) {
+ throw new ArgumentError('Invalid url');
+ }
+ }
+
+ WebSocket _socket;
+
+ bool get isOpen => _socket != null && _socket.readyState == WebSocket.OPEN;
+
+ bool get isClosed =>
+ _socket == null || _socket.readyState == WebSocket.CLOSED;
+
+ Future open() {
+ if (!isClosed) {
+ throw new TTransportError(
+ TTransportErrorType.ALREADY_OPEN, 'Socket already connected');
+ }
+
+ _socket = new WebSocket(url.toString());
+ _socket.onError.listen(_onError);
+ _socket.onOpen.listen(_onOpen);
+ _socket.onClose.listen(_onClose);
+ _socket.onMessage.listen(_onMessage);
+
+ return _socket.onOpen.first;
+ }
+
+ Future close() {
+ if (_socket != null) {
+ _socket.close();
+ return _socket.onClose.first;
+ } else {
+ return new Future.value();
+ }
+ }
+
+ void send(Uint8List data) {
+ _requests.add(data);
+ _sendRequests();
+ }
+
+ void _sendRequests() {
+ while (isOpen && _requests.isNotEmpty) {
+ Uint8List data = _requests.removeAt(0);
+ _socket.sendString(CryptoUtils.bytesToBase64(data));
+ }
+ }
+
+ void _onOpen(Event event) {
+ _onStateController.add(TSocketState.OPEN);
+ _sendRequests();
+ }
+
+ void _onClose(CloseEvent event) {
+ _socket = null;
+
+ if (_requests.isNotEmpty) {
+ _onErrorController
+ .add(new StateError('Socket was closed with pending requests'));
+ }
+ _requests.clear();
+
+ _onStateController.add(TSocketState.CLOSED);
+ }
+
+ void _onMessage(MessageEvent message) {
+ try {
+ Uint8List data =
+ new Uint8List.fromList(CryptoUtils.base64StringToBytes(message.data));
+ _onMessageController.add(data);
+ } on FormatException catch (_) {
+ var error = new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Expected a Base 64 encoded string.");
+ _onErrorController.add(error);
+ }
+ }
+
+ void _onError(Event event) {
+ close();
+ _onErrorController.add(event.toString());
+ }
+}
diff --git a/lib/dart/lib/src/console/t_tcp_socket.dart b/lib/dart/lib/src/console/t_tcp_socket.dart
new file mode 100644
index 0000000..b714803
--- /dev/null
+++ b/lib/dart/lib/src/console/t_tcp_socket.dart
@@ -0,0 +1,81 @@
+/// 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.
+
+library thrift.src.console.t_tcp_socket;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [Socket] from dart:io
+class TTcpSocket implements TSocket {
+ final StreamController<TSocketState> _onStateController;
+ Stream<TSocketState> get onState => _onStateController.stream;
+
+ final StreamController<Object> _onErrorController;
+ Stream<Object> get onError => _onErrorController.stream;
+
+ final StreamController<Uint8List> _onMessageController;
+ Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+ TTcpSocket(Socket socket)
+ : _onStateController = new StreamController.broadcast(),
+ _onErrorController = new StreamController.broadcast(),
+ _onMessageController = new StreamController.broadcast() {
+ if (socket == null) {
+ throw new ArgumentError.notNull('socket');
+ }
+
+ _socket = socket;
+ _socket.listen(_onMessage, onError: _onError, onDone: close);
+ }
+
+ Socket _socket;
+
+ bool get isOpen => _socket != null;
+
+ bool get isClosed => _socket == null;
+
+ Future open() async {
+ _onStateController.add(TSocketState.OPEN);
+ }
+
+ Future close() async {
+ if (_socket != null) {
+ await _socket.close();
+ _socket = null;
+ }
+
+ _onStateController.add(TSocketState.CLOSED);
+ }
+
+ void send(Uint8List data) {
+ _socket.add(data);
+ }
+
+ void _onMessage(List<int> message) {
+ Uint8List data = new Uint8List.fromList(message);
+ _onMessageController.add(data);
+ }
+
+ void _onError(Object error) {
+ close();
+ _onErrorController.add('$error');
+ }
+}
diff --git a/lib/dart/lib/src/console/t_web_socket.dart b/lib/dart/lib/src/console/t_web_socket.dart
new file mode 100644
index 0000000..5a549be
--- /dev/null
+++ b/lib/dart/lib/src/console/t_web_socket.dart
@@ -0,0 +1,89 @@
+/// 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.
+
+library thrift.src.console.t_web_socket;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:thrift/thrift.dart';
+
+/// A [TSocket] backed by a [WebSocket] from dart:io
+class TWebSocket implements TSocket {
+ final StreamController<TSocketState> _onStateController;
+ Stream<TSocketState> get onState => _onStateController.stream;
+
+ final StreamController<Object> _onErrorController;
+ Stream<Object> get onError => _onErrorController.stream;
+
+ final StreamController<Uint8List> _onMessageController;
+ Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+ TWebSocket(WebSocket socket)
+ : _onStateController = new StreamController.broadcast(),
+ _onErrorController = new StreamController.broadcast(),
+ _onMessageController = new StreamController.broadcast() {
+ if (socket == null) {
+ throw new ArgumentError.notNull('socket');
+ }
+
+ _socket = socket;
+ _socket.listen(_onMessage, onError: _onError, onDone: close);
+ }
+
+ WebSocket _socket;
+
+ bool get isOpen => _socket != null;
+
+ bool get isClosed => _socket == null;
+
+ Future open() async {
+ _onStateController.add(TSocketState.OPEN);
+ }
+
+ Future close() async {
+ if (_socket != null) {
+ await _socket.close();
+ _socket = null;
+ }
+
+ _onStateController.add(TSocketState.CLOSED);
+ }
+
+ void send(Uint8List data) {
+ _socket.add(CryptoUtils.bytesToBase64(data));
+ }
+
+ void _onMessage(String message) {
+ try {
+ Uint8List data =
+ new Uint8List.fromList(CryptoUtils.base64StringToBytes(message));
+ _onMessageController.add(data);
+ } on FormatException catch (_) {
+ var error = new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Expected a Base 64 encoded string.");
+ _onErrorController.add(error);
+ }
+ }
+
+ void _onError(Object error) {
+ close();
+ _onErrorController.add('$error');
+ }
+}
diff --git a/lib/dart/lib/src/protocol/t_binary_protocol.dart b/lib/dart/lib/src/protocol/t_binary_protocol.dart
new file mode 100644
index 0000000..f73223c
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_binary_protocol.dart
@@ -0,0 +1,276 @@
+/// 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.
+
+part of thrift;
+
+class TBinaryProtocolFactory implements TProtocolFactory<TBinaryProtocol> {
+ TBinaryProtocolFactory({this.strictRead: false, this.strictWrite: true});
+
+ final bool strictRead;
+ final bool strictWrite;
+
+ TBinaryProtocol getProtocol(TTransport transport) {
+ return new TBinaryProtocol(transport,
+ strictRead: strictRead, strictWrite: strictWrite);
+ }
+}
+
+/// Binary protocol implementation for Thrift.
+///
+/// Adapted from the C# version.
+class TBinaryProtocol extends TProtocol {
+ static const int VERSION_MASK = 0xffff0000;
+ static const int VERSION_1 = 0x80010000;
+
+ static const Utf8Codec _utf8Codec = const Utf8Codec();
+
+ final bool strictRead;
+ final bool strictWrite;
+
+ TBinaryProtocol(TTransport transport,
+ {this.strictRead: false, this.strictWrite: true})
+ : super(transport);
+
+ /// write
+ void writeMessageBegin(TMessage message) {
+ if (strictWrite) {
+ int version = VERSION_1 | message.type;
+ writeI32(version);
+ writeString(message.name);
+ writeI32(message.seqid);
+ } else {
+ writeString(message.name);
+ writeByte(message.type);
+ writeI32(message.seqid);
+ }
+ }
+
+ void writeMessageEnd() {}
+
+ void writeStructBegin(TStruct struct) {}
+
+ void writeStructEnd() {}
+
+ void writeFieldBegin(TField field) {
+ writeByte(field.type);
+ writeI16(field.id);
+ }
+
+ void writeFieldEnd() {}
+
+ void writeFieldStop() {
+ writeByte(TType.STOP);
+ }
+
+ void writeMapBegin(TMap map) {
+ writeByte(map.keyType);
+ writeByte(map.valueType);
+ writeI32(map.length);
+ }
+
+ void writeMapEnd() {}
+
+ void writeListBegin(TList list) {
+ writeByte(list.elementType);
+ writeI32(list.length);
+ }
+
+ void writeListEnd() {}
+
+ void writeSetBegin(TSet set) {
+ writeByte(set.elementType);
+ writeI32(set.length);
+ }
+
+ void writeSetEnd() {}
+
+ void writeBool(bool b) {
+ if (b == null) b = false;
+ writeByte(b ? 1 : 0);
+ }
+
+ final ByteData _byteOut = new ByteData(1);
+ void writeByte(int byte) {
+ if (byte == null) byte = 0;
+ _byteOut.setUint8(0, byte);
+ transport.write(_byteOut.buffer.asUint8List(), 0, 1);
+ }
+
+ final ByteData _i16Out = new ByteData(2);
+ void writeI16(int i16) {
+ if (i16 == null) i16 = 0;
+ _i16Out.setInt16(0, i16);
+ transport.write(_i16Out.buffer.asUint8List(), 0, 2);
+ }
+
+ final ByteData _i32Out = new ByteData(4);
+ void writeI32(int i32) {
+ if (i32 == null) i32 = 0;
+ _i32Out.setInt32(0, i32);
+ transport.write(_i32Out.buffer.asUint8List(), 0, 4);
+ }
+
+ final ByteData _i64Out = new ByteData(8);
+ void writeI64(int i64) {
+ if (i64 == null) i64 = 0;
+ _i64Out.setInt64(0, i64);
+ transport.write(_i64Out.buffer.asUint8List(), 0, 8);
+ }
+
+ void writeString(String s) {
+ var bytes = s != null ? _utf8Codec.encode(s) : new Uint8List.fromList([]);
+ writeI32(bytes.length);
+ transport.write(bytes, 0, bytes.length);
+ }
+
+ final ByteData _doubleOut = new ByteData(8);
+ void writeDouble(double d) {
+ if (d == null) d = 0.0;
+ _doubleOut.setFloat64(0, d);
+ transport.write(_doubleOut.buffer.asUint8List(), 0, 8);
+ }
+
+ void writeBinary(Uint8List bytes) {
+ var length = bytes.length;
+ writeI32(length);
+ transport.write(bytes, 0, length);
+ }
+
+ /// read
+ TMessage readMessageBegin() {
+ String name;
+ int type;
+ int seqid;
+
+ int size = readI32();
+ if (size < 0) {
+ int version = size & VERSION_MASK;
+ if (version != VERSION_1) {
+ throw new TProtocolError(TProtocolErrorType.BAD_VERSION,
+ "Bad version in readMessageBegin: $version");
+ }
+ type = size & 0x000000ff;
+ name = readString();
+ seqid = readI32();
+ } else {
+ if (strictRead) {
+ throw new TProtocolError(TProtocolErrorType.BAD_VERSION,
+ "Missing version in readMessageBegin");
+ }
+ name = _readString(size);
+ type = readByte();
+ seqid = readI32();
+ }
+ return new TMessage(name, type, seqid);
+ }
+
+ void readMessageEnd() {}
+
+ TStruct readStructBegin() {
+ return new TStruct();
+ }
+
+ void readStructEnd() {}
+
+ TField readFieldBegin() {
+ String name = "";
+ int type = readByte();
+ int id = type != TType.STOP ? readI16() : 0;
+
+ return new TField(name, type, id);
+ }
+
+ void readFieldEnd() {}
+
+ TMap readMapBegin() {
+ int keyType = readByte();
+ int valueType = readByte();
+ int length = readI32();
+
+ return new TMap(keyType, valueType, length);
+ }
+
+ void readMapEnd() {}
+
+ TList readListBegin() {
+ int elementType = readByte();
+ int length = readI32();
+
+ return new TList(elementType, length);
+ }
+
+ void readListEnd() {}
+
+ TSet readSetBegin() {
+ int elementType = readByte();
+ int length = readI32();
+
+ return new TSet(elementType, length);
+ }
+
+ void readSetEnd() {}
+
+ bool readBool() => readByte() == 1;
+
+ final Uint8List _byteIn = new Uint8List(1);
+ int readByte() {
+ transport.readAll(_byteIn, 0, 1);
+ return _byteIn.buffer.asByteData().getUint8(0);
+ }
+
+ final Uint8List _i16In = new Uint8List(2);
+ int readI16() {
+ transport.readAll(_i16In, 0, 2);
+ return _i16In.buffer.asByteData().getInt16(0);
+ }
+
+ final Uint8List _i32In = new Uint8List(4);
+ int readI32() {
+ transport.readAll(_i32In, 0, 4);
+ return _i32In.buffer.asByteData().getInt32(0);
+ }
+
+ final Uint8List _i64In = new Uint8List(8);
+ int readI64() {
+ transport.readAll(_i64In, 0, 8);
+ return _i64In.buffer.asByteData().getInt64(0);
+ }
+
+ final Uint8List _doubleIn = new Uint8List(8);
+ double readDouble() {
+ transport.readAll(_doubleIn, 0, 8);
+ return _doubleIn.buffer.asByteData().getFloat64(0);
+ }
+
+ String readString() {
+ int size = readI32();
+ return _readString(size);
+ }
+
+ String _readString(int size) {
+ Uint8List stringIn = new Uint8List(size);
+ transport.readAll(stringIn, 0, size);
+ return _utf8Codec.decode(stringIn);
+ }
+
+ Uint8List readBinary() {
+ int length = readI32();
+ Uint8List binaryIn = new Uint8List(length);
+ transport.readAll(binaryIn, 0, length);
+ return binaryIn;
+ }
+}
diff --git a/lib/dart/lib/src/protocol/t_field.dart b/lib/dart/lib/src/protocol/t_field.dart
new file mode 100644
index 0000000..444b4e5
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_field.dart
@@ -0,0 +1,26 @@
+/// 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.
+
+part of thrift;
+
+class TField {
+ final String name;
+ final int type;
+ final int id;
+
+ TField(this.name, this.type, this.id);
+}
diff --git a/lib/dart/lib/src/protocol/t_json_protocol.dart b/lib/dart/lib/src/protocol/t_json_protocol.dart
new file mode 100644
index 0000000..4fa6499
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_json_protocol.dart
@@ -0,0 +1,754 @@
+/// 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.
+
+part of thrift;
+
+class TJsonProtocolFactory implements TProtocolFactory<TJsonProtocol> {
+ TJsonProtocol getProtocol(TTransport transport) {
+ return new TJsonProtocol(transport);
+ }
+}
+
+/// JSON protocol implementation for Thrift.
+///
+/// Adapted from the C# version.
+class TJsonProtocol extends TProtocol {
+ static const int VERSION_1 = 1;
+
+ static const Utf8Codec utf8Codec = const Utf8Codec();
+
+ _BaseContext _context;
+ _BaseContext _rootContext;
+ _LookaheadReader _reader;
+
+ final List<_BaseContext> _contextStack = [];
+ final Uint8List _tempBuffer = new Uint8List(4);
+
+ TJsonProtocol(TTransport transport) : super(transport) {
+ _rootContext = new _BaseContext(this);
+ _reader = new _LookaheadReader(this);
+ }
+
+ void _pushContext(_BaseContext c) {
+ _contextStack.add(c);
+ _context = c;
+ }
+
+ void _popContext() {
+ _contextStack.removeLast();
+ _context = _contextStack.isEmpty ? _rootContext : _contextStack.last;
+ }
+
+ void _resetContext() {
+ _contextStack.clear();
+ _context = _rootContext;
+ }
+
+ /// Read a byte that must match [char]; otherwise throw a [TProtocolError].
+ void _readJsonSyntaxChar(int charByte) {
+ int byte = _reader.read();
+ if (byte != charByte) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Expected character ${new String.fromCharCode(charByte)} but found: ${new String.fromCharCode(byte)}");
+ }
+ }
+
+ int _hexVal(int byte) {
+ if (byte >= _Constants.HEX_0_BYTES[0] &&
+ byte <= _Constants.HEX_9_BYTES[0]) {
+ return byte - _Constants.HEX_0_BYTES[0];
+ } else if (byte >= _Constants.HEX_A_BYTES[0] &&
+ byte <= _Constants.HEX_F_BYTES[0]) {
+ byte += 10;
+ return byte - _Constants.HEX_A_BYTES[0];
+ } else {
+ throw new TProtocolError(
+ TProtocolErrorType.INVALID_DATA, "Expected hex character");
+ }
+ }
+
+ int _hexChar(int byte) => byte.toRadixString(16).codeUnitAt(0);
+
+ /// write
+
+ /// Write the [bytes] as JSON characters, escaping as needed.
+ void _writeJsonString(Uint8List bytes) {
+ _context.write();
+ transport.writeAll(_Constants.QUOTE_BYTES);
+
+ int length = bytes.length;
+ for (int i = 0; i < length; i++) {
+ int byte = bytes[i];
+ if ((byte & 0x00FF) >= 0x30) {
+ if (byte == _Constants.BACKSLASH_BYTES[0]) {
+ transport.writeAll(_Constants.BACKSLASH_BYTES);
+ transport.writeAll(_Constants.BACKSLASH_BYTES);
+ } else {
+ transport.write(bytes, i, 1);
+ }
+ } else {
+ _tempBuffer[0] = _Constants.JSON_CHAR_TABLE[byte];
+ if (_tempBuffer[0] == 1) {
+ transport.write(bytes, i, 1);
+ } else if (_tempBuffer[0] > 1) {
+ transport.writeAll(_Constants.BACKSLASH_BYTES);
+ transport.write(_tempBuffer, 0, 1);
+ } else {
+ transport.writeAll(_Constants.ESCSEQ_BYTES);
+ _tempBuffer[0] = _hexChar(byte >> 4);
+ _tempBuffer[1] = _hexChar(byte);
+ transport.write(_tempBuffer, 0, 2);
+ }
+ }
+ }
+
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+
+ void _writeJsonInteger(int i) {
+ if (i == null) i = 0;
+
+ _context.write();
+ String str = i.toString();
+
+ if (_context.escapeNumbers) {
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+ transport.writeAll(utf8Codec.encode(str));
+ if (_context.escapeNumbers) {
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+ }
+
+ void _writeJsonDouble(double d) {
+ if (d == null) d = 0.0;
+
+ _context.write();
+ String str = d.toString();
+ bool escapeNumbers = d.isNaN || d.isInfinite || _context.escapeNumbers;
+
+ if (escapeNumbers) {
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+ transport.writeAll(utf8Codec.encode(str));
+ if (escapeNumbers) {
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+ }
+
+ void _writeJsonBase64(Uint8List bytes) {
+ _context.write();
+ transport.writeAll(_Constants.QUOTE_BYTES);
+
+ String base64 = CryptoUtils.bytesToBase64(bytes);
+ transport.writeAll(utf8Codec.encode(base64));
+
+ transport.writeAll(_Constants.QUOTE_BYTES);
+ }
+
+ void _writeJsonObjectStart() {
+ _context.write();
+ transport.writeAll(_Constants.LBRACE_BYTES);
+ _pushContext(new _PairContext(this));
+ }
+
+ void _writeJsonObjectEnd() {
+ _popContext();
+ transport.writeAll(_Constants.RBRACE_BYTES);
+ }
+
+ void _writeJsonArrayStart() {
+ _context.write();
+ transport.writeAll(_Constants.LBRACKET_BYTES);
+ _pushContext(new _ListContext(this));
+ }
+
+ void _writeJsonArrayEnd() {
+ _popContext();
+ transport.writeAll(_Constants.RBRACKET_BYTES);
+ }
+
+ void writeMessageBegin(TMessage message) {
+ _resetContext();
+
+ _writeJsonArrayStart();
+ _writeJsonInteger(VERSION_1);
+
+ _writeJsonString(utf8Codec.encode(message.name));
+ _writeJsonInteger(message.type);
+ _writeJsonInteger(message.seqid);
+ }
+
+ void writeMessageEnd() {
+ _writeJsonArrayEnd();
+ }
+
+ void writeStructBegin(TStruct struct) {
+ _writeJsonObjectStart();
+ }
+
+ void writeStructEnd() {
+ _writeJsonObjectEnd();
+ }
+
+ void writeFieldBegin(TField field) {
+ _writeJsonInteger(field.id);
+ _writeJsonObjectStart();
+ _writeJsonString(_Constants.getTypeNameBytesForTypeId(field.type));
+ }
+
+ void writeFieldEnd() {
+ _writeJsonObjectEnd();
+ }
+
+ void writeFieldStop() {}
+
+ void writeMapBegin(TMap map) {
+ _writeJsonArrayStart();
+ _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.keyType));
+ _writeJsonString(_Constants.getTypeNameBytesForTypeId(map.valueType));
+ _writeJsonInteger(map.length);
+ _writeJsonObjectStart();
+ }
+
+ void writeMapEnd() {
+ _writeJsonObjectEnd();
+ _writeJsonArrayEnd();
+ }
+
+ void writeListBegin(TList list) {
+ _writeJsonArrayStart();
+ _writeJsonString(_Constants.getTypeNameBytesForTypeId(list.elementType));
+ _writeJsonInteger(list.length);
+ }
+
+ void writeListEnd() {
+ _writeJsonArrayEnd();
+ }
+
+ void writeSetBegin(TSet set) {
+ _writeJsonArrayStart();
+ _writeJsonString(_Constants.getTypeNameBytesForTypeId(set.elementType));
+ _writeJsonInteger(set.length);
+ }
+
+ void writeSetEnd() {
+ _writeJsonArrayEnd();
+ }
+
+ void writeBool(bool b) {
+ if (b == null) b = false;
+ _writeJsonInteger(b ? 1 : 0);
+ }
+
+ void writeByte(int b) {
+ _writeJsonInteger(b);
+ }
+
+ void writeI16(int i16) {
+ _writeJsonInteger(i16);
+ }
+
+ void writeI32(int i32) {
+ _writeJsonInteger(i32);
+ }
+
+ void writeI64(int i64) {
+ _writeJsonInteger(i64);
+ }
+
+ void writeDouble(double d) {
+ _writeJsonDouble(d);
+ }
+
+ void writeString(String s) {
+ var bytes = s != null ? utf8Codec.encode(s) : new Uint8List.fromList([]);
+ _writeJsonString(bytes);
+ }
+
+ void writeBinary(Uint8List bytes) {
+ _writeJsonBase64(bytes);
+ }
+
+ /// read
+
+ Uint8List _readJsonString({bool skipContext: false}) {
+ List<int> bytes = [];
+
+ if (!skipContext) {
+ _context.read();
+ }
+
+ _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+ while (true) {
+ int byte = _reader.read();
+ if (byte == _Constants.QUOTE_BYTES[0]) {
+ break;
+ }
+
+ // escaped?
+ if (byte != _Constants.ESCSEQ_BYTES[0]) {
+ bytes.add(byte);
+ continue;
+ }
+
+ byte = _reader.read();
+
+ // distinguish between \u00XX and control chars like \n
+ if (byte != _Constants.ESCSEQ_BYTES[1]) {
+ String char = new String.fromCharCode(byte);
+ int offset = _Constants.ESCAPE_CHARS.indexOf(char);
+ if (offset == -1) {
+ throw new TProtocolError(
+ TProtocolErrorType.INVALID_DATA, "Expected control char");
+ }
+ byte = _Constants.ESCAPE_CHAR_VALS.codeUnitAt(offset);
+ bytes.add(byte);
+ continue;
+ }
+
+ // it's \u00XX
+ _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]);
+ _readJsonSyntaxChar(_Constants.HEX_0_BYTES[0]);
+ transport.readAll(_tempBuffer, 0, 2);
+ byte = _hexVal(_tempBuffer[0]) << 4 + _hexVal(_tempBuffer[1]);
+ bytes.add(byte);
+ }
+
+ return new Uint8List.fromList(bytes);
+ }
+
+ String _readJsonNumericChars() {
+ StringBuffer buffer = new StringBuffer();
+ while (true) {
+ if (!_Constants.isJsonNumeric(_reader.peek())) {
+ break;
+ }
+ buffer.write(new String.fromCharCode(_reader.read()));
+ }
+ return buffer.toString();
+ }
+
+ int _readJsonInteger() {
+ _context.read();
+
+ if (_context.escapeNumbers) {
+ _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+ }
+ String str = _readJsonNumericChars();
+ if (_context.escapeNumbers) {
+ _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+ }
+
+ try {
+ return int.parse(str);
+ } on FormatException catch (_) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Bad data encounted in numeric data");
+ }
+ }
+
+ double _readJsonDouble() {
+ _context.read();
+
+ if (_reader.peek() == _Constants.QUOTE_BYTES[0]) {
+ Uint8List bytes = _readJsonString(skipContext: true);
+ double d = double.parse(utf8Codec.decode(bytes), (_) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Bad data encounted in numeric data");
+ });
+ if (!_context.escapeNumbers && !d.isNaN && !d.isInfinite) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Numeric data unexpectedly quoted");
+ }
+ return d;
+ } else {
+ if (_context.escapeNumbers) {
+ // This will throw - we should have had a quote if escapeNumbers == true
+ _readJsonSyntaxChar(_Constants.QUOTE_BYTES[0]);
+ }
+ return double.parse(_readJsonNumericChars(), (_) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Bad data encounted in numeric data");
+ });
+ }
+ }
+
+ Uint8List _readJsonBase64() {
+ // convert UTF-8 bytes of a Base 64 encoded string to binary bytes
+ Uint8List base64Bytes = _readJsonString();
+ String base64 = utf8Codec.decode(base64Bytes);
+
+ return new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64));
+ }
+
+ void _readJsonObjectStart() {
+ _context.read();
+ _readJsonSyntaxChar(_Constants.LBRACE_BYTES[0]);
+ _pushContext(new _PairContext(this));
+ }
+
+ void _readJsonObjectEnd() {
+ _readJsonSyntaxChar(_Constants.RBRACE_BYTES[0]);
+ _popContext();
+ }
+
+ void _readJsonArrayStart() {
+ _context.read();
+ _readJsonSyntaxChar(_Constants.LBRACKET_BYTES[0]);
+ _pushContext(new _ListContext(this));
+ }
+
+ void _readJsonArrayEnd() {
+ _readJsonSyntaxChar(_Constants.RBRACKET_BYTES[0]);
+ _popContext();
+ }
+
+ TMessage readMessageBegin() {
+ _resetContext();
+
+ _readJsonArrayStart();
+ if (_readJsonInteger() != VERSION_1) {
+ throw new TProtocolError(
+ TProtocolErrorType.BAD_VERSION, "Message contained bad version.");
+ }
+
+ Uint8List buffer = _readJsonString();
+ String name = utf8Codec.decode(buffer);
+ int type = _readJsonInteger();
+ int seqid = _readJsonInteger();
+
+ return new TMessage(name, type, seqid);
+ }
+
+ void readMessageEnd() {
+ _readJsonArrayEnd();
+ }
+
+ TStruct readStructBegin() {
+ _readJsonObjectStart();
+ return new TStruct();
+ }
+
+ void readStructEnd() {
+ _readJsonObjectEnd();
+ }
+
+ TField readFieldBegin() {
+ String name = "";
+ int type = TType.STOP;
+ int id = 0;
+
+ if (_reader.peek() != _Constants.RBRACE_BYTES[0]) {
+ id = _readJsonInteger();
+ _readJsonObjectStart();
+ type = _Constants.getTypeIdForTypeName(_readJsonString());
+ }
+
+ return new TField(name, type, id);
+ }
+
+ void readFieldEnd() {
+ _readJsonObjectEnd();
+ }
+
+ TMap readMapBegin() {
+ _readJsonArrayStart();
+ int keyType = _Constants.getTypeIdForTypeName(_readJsonString());
+ int valueType = _Constants.getTypeIdForTypeName(_readJsonString());
+ int length = _readJsonInteger();
+ _readJsonObjectStart();
+
+ return new TMap(keyType, valueType, length);
+ }
+
+ void readMapEnd() {
+ _readJsonObjectEnd();
+ _readJsonArrayEnd();
+ }
+
+ TList readListBegin() {
+ _readJsonArrayStart();
+ int elementType = _Constants.getTypeIdForTypeName(_readJsonString());
+ int length = _readJsonInteger();
+
+ return new TList(elementType, length);
+ }
+
+ void readListEnd() {
+ _readJsonArrayEnd();
+ }
+
+ TSet readSetBegin() {
+ _readJsonArrayStart();
+ int elementType = _Constants.getTypeIdForTypeName(_readJsonString());
+ int length = _readJsonInteger();
+
+ return new TSet(elementType, length);
+ }
+
+ void readSetEnd() {
+ _readJsonArrayEnd();
+ }
+
+ bool readBool() {
+ return _readJsonInteger() == 0 ? false : true;
+ }
+
+ int readByte() {
+ return _readJsonInteger();
+ }
+
+ int readI16() {
+ return _readJsonInteger();
+ }
+
+ int readI32() {
+ return _readJsonInteger();
+ }
+
+ int readI64() {
+ return _readJsonInteger();
+ }
+
+ double readDouble() {
+ return _readJsonDouble();
+ }
+
+ String readString() {
+ return utf8Codec.decode(_readJsonString());
+ }
+
+ Uint8List readBinary() {
+ return new Uint8List.fromList(_readJsonBase64());
+ }
+}
+
+class _Constants {
+ static const utf8codec = const Utf8Codec();
+
+ static final Uint8List HEX_0_BYTES = new Uint8List.fromList('0'.codeUnits);
+ static final Uint8List HEX_9_BYTES = new Uint8List.fromList('9'.codeUnits);
+ static final Uint8List HEX_A_BYTES = new Uint8List.fromList('a'.codeUnits);
+ static final Uint8List HEX_F_BYTES = new Uint8List.fromList('f'.codeUnits);
+ static final Uint8List COMMA_BYTES = new Uint8List.fromList(','.codeUnits);
+ static final Uint8List COLON_BYTES = new Uint8List.fromList(':'.codeUnits);
+ static final Uint8List LBRACE_BYTES = new Uint8List.fromList('{'.codeUnits);
+ static final Uint8List RBRACE_BYTES = new Uint8List.fromList('}'.codeUnits);
+ static final Uint8List LBRACKET_BYTES = new Uint8List.fromList('['.codeUnits);
+ static final Uint8List RBRACKET_BYTES = new Uint8List.fromList(']'.codeUnits);
+ static final Uint8List QUOTE_BYTES = new Uint8List.fromList('"'.codeUnits);
+ static final Uint8List BACKSLASH_BYTES =
+ new Uint8List.fromList(r'\'.codeUnits);
+
+ static final ESCSEQ_BYTES = new Uint8List.fromList(r'\u00'.codeUnits);
+
+ static final Uint8List JSON_CHAR_TABLE = new Uint8List.fromList([
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+ 'b'.codeUnitAt(0), 't'.codeUnitAt(0), 'n'.codeUnitAt(0), 0, // 4 bytes
+ 'f'.codeUnitAt(0), 'r'.codeUnitAt(0), 0, 0, // 4 bytes
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+ 0, 0, 0, 0, 0, 0, 0, 0, // 8 bytes
+ 1, 1, '"'.codeUnitAt(0), 1, 1, 1, 1, 1, // 8 bytes
+ 1, 1, 1, 1, 1, 1, 1, 1 // 8 bytes
+ ]);
+
+ static const String ESCAPE_CHARS = r'"\/bfnrt';
+ static const String ESCAPE_CHAR_VALS = '"\\/\b\f\n\r\t';
+
+ static const String NAME_BOOL = 'tf';
+ static const String NAME_BYTE = 'i8';
+ static const String NAME_I16 = 'i16';
+ static const String NAME_I32 = 'i32';
+ static const String NAME_I64 = 'i64';
+ static const String NAME_DOUBLE = 'dbl';
+ static const String NAME_STRUCT = 'rec';
+ static const String NAME_STRING = 'str';
+ static const String NAME_MAP = 'map';
+ static const String NAME_LIST = 'lst';
+ static const String NAME_SET = 'set';
+
+ static final Map<int, Uint8List> _TYPE_ID_TO_NAME_BYTES =
+ new Map.unmodifiable({
+ TType.BOOL: new Uint8List.fromList(NAME_BOOL.codeUnits),
+ TType.BYTE: new Uint8List.fromList(NAME_BYTE.codeUnits),
+ TType.I16: new Uint8List.fromList(NAME_I16.codeUnits),
+ TType.I32: new Uint8List.fromList(NAME_I32.codeUnits),
+ TType.I64: new Uint8List.fromList(NAME_I64.codeUnits),
+ TType.DOUBLE: new Uint8List.fromList(NAME_DOUBLE.codeUnits),
+ TType.STRING: new Uint8List.fromList(NAME_STRING.codeUnits),
+ TType.STRUCT: new Uint8List.fromList(NAME_STRUCT.codeUnits),
+ TType.MAP: new Uint8List.fromList(NAME_MAP.codeUnits),
+ TType.SET: new Uint8List.fromList(NAME_SET.codeUnits),
+ TType.LIST: new Uint8List.fromList(NAME_LIST.codeUnits)
+ });
+
+ static Uint8List getTypeNameBytesForTypeId(int typeId) {
+ if (!_TYPE_ID_TO_NAME_BYTES.containsKey(typeId)) {
+ throw new TProtocolError(
+ TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type");
+ }
+
+ return _TYPE_ID_TO_NAME_BYTES[typeId];
+ }
+
+ static final Map<String, int> _NAME_TO_TYPE_ID = new Map.unmodifiable({
+ NAME_BOOL: TType.BOOL,
+ NAME_BYTE: TType.BYTE,
+ NAME_I16: TType.I16,
+ NAME_I32: TType.I32,
+ NAME_I64: TType.I64,
+ NAME_DOUBLE: TType.DOUBLE,
+ NAME_STRING: TType.STRING,
+ NAME_STRUCT: TType.STRUCT,
+ NAME_MAP: TType.MAP,
+ NAME_SET: TType.SET,
+ NAME_LIST: TType.LIST
+ });
+
+ static int getTypeIdForTypeName(Uint8List bytes) {
+ String name = utf8codec.decode(bytes);
+ if (!_NAME_TO_TYPE_ID.containsKey(name)) {
+ throw new TProtocolError(
+ TProtocolErrorType.NOT_IMPLEMENTED, "Unrecognized type");
+ }
+
+ return _NAME_TO_TYPE_ID[name];
+ }
+
+ static final Set<int> _JSON_NUMERICS = new Set.from([
+ '+'.codeUnitAt(0),
+ '-'.codeUnitAt(0),
+ '.'.codeUnitAt(0),
+ '0'.codeUnitAt(0),
+ '1'.codeUnitAt(0),
+ '2'.codeUnitAt(0),
+ '3'.codeUnitAt(0),
+ '4'.codeUnitAt(0),
+ '5'.codeUnitAt(0),
+ '6'.codeUnitAt(0),
+ '7'.codeUnitAt(0),
+ '8'.codeUnitAt(0),
+ '9'.codeUnitAt(0),
+ 'E'.codeUnitAt(0),
+ 'e'.codeUnitAt(0)
+ ]);
+
+ static bool isJsonNumeric(int byte) {
+ return _JSON_NUMERICS.contains(byte);
+ }
+}
+
+class _LookaheadReader {
+ final TJsonProtocol protocol;
+
+ _LookaheadReader(this.protocol);
+
+ bool _hasData = false;
+ final Uint8List _data = new Uint8List(1);
+
+ int read() {
+ if (_hasData) {
+ _hasData = false;
+ } else {
+ protocol.transport.readAll(_data, 0, 1);
+ }
+
+ return _data[0];
+ }
+
+ int peek() {
+ if (!_hasData) {
+ protocol.transport.readAll(_data, 0, 1);
+ }
+ _hasData = true;
+
+ return _data[0];
+ }
+}
+
+class _BaseContext {
+ final TJsonProtocol protocol;
+
+ _BaseContext(this.protocol);
+
+ void write() {}
+
+ void read() {}
+
+ bool get escapeNumbers => false;
+
+ String toString() => 'BaseContext';
+}
+
+class _ListContext extends _BaseContext {
+ _ListContext(TJsonProtocol protocol) : super(protocol);
+
+ bool _first = true;
+
+ void write() {
+ if (_first) {
+ _first = false;
+ } else {
+ protocol.transport.writeAll(_Constants.COMMA_BYTES);
+ }
+ }
+
+ void read() {
+ if (_first) {
+ _first = false;
+ } else {
+ protocol._readJsonSyntaxChar(_Constants.COMMA_BYTES[0]);
+ }
+ }
+
+ String toString() => 'ListContext';
+}
+
+class _PairContext extends _BaseContext {
+ _PairContext(TJsonProtocol protocol) : super(protocol);
+
+ bool _first = true;
+ bool _colon = true;
+
+ Uint8List get symbolBytes =>
+ _colon ? _Constants.COLON_BYTES : _Constants.COMMA_BYTES;
+
+ void write() {
+ if (_first) {
+ _first = false;
+ _colon = true;
+ } else {
+ protocol.transport.writeAll(symbolBytes);
+ _colon = !_colon;
+ }
+ }
+
+ void read() {
+ if (_first) {
+ _first = false;
+ _colon = true;
+ } else {
+ protocol._readJsonSyntaxChar(symbolBytes[0]);
+ _colon = !_colon;
+ }
+ }
+
+ bool get escapeNumbers => _colon;
+
+ String toString() => 'PairContext';
+}
diff --git a/lib/dart/lib/src/protocol/t_list.dart b/lib/dart/lib/src/protocol/t_list.dart
new file mode 100644
index 0000000..49f4673
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_list.dart
@@ -0,0 +1,25 @@
+/// 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.
+
+part of thrift;
+
+class TList {
+ final int elementType;
+ final int length;
+
+ TList(this.elementType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_map.dart b/lib/dart/lib/src/protocol/t_map.dart
new file mode 100644
index 0000000..efdf681
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_map.dart
@@ -0,0 +1,26 @@
+/// 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.
+
+part of thrift;
+
+class TMap {
+ final int keyType;
+ final int valueType;
+ final int length;
+
+ TMap(this.keyType, this.valueType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_message.dart b/lib/dart/lib/src/protocol/t_message.dart
new file mode 100644
index 0000000..cc7b886
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_message.dart
@@ -0,0 +1,35 @@
+/// 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.
+
+part of thrift;
+
+class TMessageType {
+ static const int CALL = 1;
+ static const int REPLY = 2;
+ static const int EXCEPTION = 3;
+ static const int ONEWAY = 4;
+}
+
+class TMessage {
+ final String name;
+ final int type;
+ final int seqid;
+
+ TMessage(this.name, this.type, this.seqid);
+
+ String toString() => "<TMessage name: '$name' type: $type seqid: $seqid>";
+}
diff --git a/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart
new file mode 100644
index 0000000..078a6d7
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_multiplexed_protocol.dart
@@ -0,0 +1,43 @@
+/// 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.
+
+part of thrift;
+
+/// Adapted from the C# version.
+class TMultiplexedProtocol extends TProtocolDecorator {
+ static const SEPARATOR = ':';
+
+ final String _serviceName;
+
+ TMultiplexedProtocol(TProtocol protocol, String serviceName)
+ : _serviceName = serviceName,
+ super(protocol) {
+ if (serviceName == null) {
+ throw new ArgumentError.notNull("serviceName");
+ }
+ }
+
+ void writeMessageBegin(TMessage message) {
+ if (message.type == TMessageType.CALL ||
+ message.type == TMessageType.ONEWAY) {
+ String name = _serviceName + SEPARATOR + message.name;
+ message = new TMessage(name, message.type, message.seqid);
+ }
+
+ super.writeMessageBegin(message);
+ }
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol.dart b/lib/dart/lib/src/protocol/t_protocol.dart
new file mode 100644
index 0000000..f49c032
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol.dart
@@ -0,0 +1,95 @@
+/// 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.
+
+part of thrift;
+
+abstract class TProtocol {
+ final TTransport transport;
+
+ TProtocol(this.transport);
+
+ /// Write
+ void writeMessageBegin(TMessage message);
+ void writeMessageEnd();
+
+ void writeStructBegin(TStruct struct);
+ void writeStructEnd();
+
+ void writeFieldBegin(TField field);
+ void writeFieldEnd();
+ void writeFieldStop();
+
+ void writeMapBegin(TMap map);
+ void writeMapEnd();
+
+ void writeListBegin(TList list);
+ void writeListEnd();
+
+ void writeSetBegin(TSet set);
+ void writeSetEnd();
+
+ void writeBool(bool b);
+
+ void writeByte(int b);
+
+ void writeI16(int i16);
+
+ void writeI32(int i32);
+
+ void writeI64(int i64);
+
+ void writeDouble(double d);
+
+ void writeString(String str);
+
+ void writeBinary(Uint8List bytes);
+
+ /// Read
+ TMessage readMessageBegin();
+ void readMessageEnd();
+
+ TStruct readStructBegin();
+ void readStructEnd();
+
+ TField readFieldBegin();
+ void readFieldEnd();
+
+ TMap readMapBegin();
+ void readMapEnd();
+
+ TList readListBegin();
+ void readListEnd();
+
+ TSet readSetBegin();
+ void readSetEnd();
+
+ bool readBool();
+
+ int readByte();
+
+ int readI16();
+
+ int readI32();
+
+ int readI64();
+
+ double readDouble();
+
+ String readString();
+
+ Uint8List readBinary();
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_decorator.dart b/lib/dart/lib/src/protocol/t_protocol_decorator.dart
new file mode 100644
index 0000000..9cd02f6
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_decorator.dart
@@ -0,0 +1,150 @@
+/// 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.
+
+part of thrift;
+
+/// Forward all operations to the wrapped protocol. Used as a base class.
+///
+/// Adapted from the C# version.
+class TProtocolDecorator extends TProtocol {
+ final TProtocol _protocol;
+
+ TProtocolDecorator(TProtocol protocol)
+ : _protocol = protocol,
+ super(protocol.transport);
+
+ /// Write
+
+ void writeMessageBegin(TMessage message) {
+ _protocol.writeMessageBegin(message);
+ }
+
+ void writeMessageEnd() {
+ _protocol.writeMessageEnd();
+ }
+
+ void writeStructBegin(TStruct struct) {
+ _protocol.writeStructBegin(struct);
+ }
+
+ void writeStructEnd() {
+ _protocol.writeStructEnd();
+ }
+
+ void writeFieldBegin(TField field) {
+ _protocol.writeFieldBegin(field);
+ }
+
+ void writeFieldEnd() {
+ _protocol.writeFieldEnd();
+ }
+
+ void writeFieldStop() {
+ _protocol.writeFieldStop();
+ }
+
+ void writeMapBegin(TMap map) {
+ _protocol.writeMapBegin(map);
+ }
+
+ void writeMapEnd() {
+ _protocol.writeMapEnd();
+ }
+
+ void writeListBegin(TList list) {
+ _protocol.writeListBegin(list);
+ }
+
+ void writeListEnd() {
+ _protocol.writeListEnd();
+ }
+
+ void writeSetBegin(TSet set) {
+ _protocol.writeSetBegin(set);
+ }
+
+ void writeSetEnd() {
+ _protocol.writeSetEnd();
+ }
+
+ void writeBool(bool b) {
+ _protocol.writeBool(b);
+ }
+
+ void writeByte(int b) {
+ _protocol.writeByte(b);
+ }
+
+ void writeI16(int i16) {
+ _protocol.writeI16(i16);
+ }
+
+ void writeI32(int i32) {
+ _protocol.writeI32(i32);
+ }
+
+ void writeI64(int i64) {
+ _protocol.writeI64(i64);
+ }
+
+ void writeDouble(double d) {
+ _protocol.writeDouble(d);
+ }
+
+ void writeString(String str) {
+ _protocol.writeString(str);
+ }
+
+ void writeBinary(Uint8List bytes) {
+ _protocol.writeBinary(bytes);
+ }
+
+ /// Read
+ TMessage readMessageBegin() => _protocol.readMessageBegin();
+ void readMessageEnd() => _protocol.readMessageEnd();
+
+ TStruct readStructBegin() => _protocol.readStructBegin();
+ void readStructEnd() => _protocol.readStructEnd();
+
+ TField readFieldBegin() => _protocol.readFieldBegin();
+ void readFieldEnd() => _protocol.readFieldEnd();
+
+ TMap readMapBegin() => _protocol.readMapBegin();
+ void readMapEnd() => _protocol.readMapEnd();
+
+ TList readListBegin() => _protocol.readListBegin();
+ void readListEnd() => _protocol.readListEnd();
+
+ TSet readSetBegin() => _protocol.readSetBegin();
+ void readSetEnd() => _protocol.readSetEnd();
+
+ bool readBool() => _protocol.readBool();
+
+ int readByte() => _protocol.readByte();
+
+ int readI16() => _protocol.readI16();
+
+ int readI32() => _protocol.readI32();
+
+ int readI64() => _protocol.readI64();
+
+ double readDouble() => _protocol.readDouble();
+
+ String readString() => _protocol.readString();
+
+ Uint8List readBinary() => _protocol.readBinary();
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_error.dart b/lib/dart/lib/src/protocol/t_protocol_error.dart
new file mode 100644
index 0000000..456baeb
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_error.dart
@@ -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.
+
+part of thrift;
+
+class TProtocolErrorType {
+ static const int UNKNOWN = 0;
+ static const int INVALID_DATA = 1;
+ static const int NEGATIVE_SIZE = 2;
+ static const int SIZE_LIMIT = 3;
+ static const int BAD_VERSION = 4;
+ static const int NOT_IMPLEMENTED = 5;
+ static const int DEPTH_LIMIT = 6;
+}
+
+class TProtocolError extends TError {
+ TProtocolError([int type = TProtocolErrorType.UNKNOWN, String message = ""])
+ : super(type, message);
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_factory.dart b/lib/dart/lib/src/protocol/t_protocol_factory.dart
new file mode 100644
index 0000000..922c6cb
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_factory.dart
@@ -0,0 +1,22 @@
+/// 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.
+
+part of thrift;
+
+abstract class TProtocolFactory<T extends TProtocol> {
+ T getProtocol(TTransport transport);
+}
diff --git a/lib/dart/lib/src/protocol/t_protocol_util.dart b/lib/dart/lib/src/protocol/t_protocol_util.dart
new file mode 100644
index 0000000..ad20068
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_protocol_util.dart
@@ -0,0 +1,107 @@
+/// 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.
+
+part of thrift;
+
+class TProtocolUtil {
+ // equal to JavaScript Number.MAX_SAFE_INTEGER, 2^53 - 1
+ static const int defaultRecursionLimit = 9007199254740991;
+
+ static int maxRecursionLimit = defaultRecursionLimit;
+
+ static skip(TProtocol prot, int type) {
+ _skip(prot, type, maxRecursionLimit);
+ }
+
+ static _skip(TProtocol prot, int type, int recursionLimit) {
+ if (recursionLimit <= 0) {
+ throw new TProtocolError(
+ TProtocolErrorType.DEPTH_LIMIT, "Depth limit exceeded");
+ }
+
+ switch (type) {
+ case TType.BOOL:
+ prot.readBool();
+ break;
+
+ case TType.BYTE:
+ prot.readByte();
+ break;
+
+ case TType.I16:
+ prot.readI16();
+ break;
+
+ case TType.I32:
+ prot.readI32();
+ break;
+
+ case TType.I64:
+ prot.readI64();
+ break;
+
+ case TType.DOUBLE:
+ prot.readDouble();
+ break;
+
+ case TType.STRING:
+ prot.readBinary();
+ break;
+
+ case TType.STRUCT:
+ prot.readStructBegin();
+ while (true) {
+ TField field = prot.readFieldBegin();
+ if (field.type == TType.STOP) {
+ break;
+ }
+ _skip(prot, field.type, recursionLimit - 1);
+ prot.readFieldEnd();
+ }
+ prot.readStructEnd();
+ break;
+
+ case TType.MAP:
+ TMap map = prot.readMapBegin();
+ for (int i = 0; i < map.length; i++) {
+ _skip(prot, map.keyType, recursionLimit - 1);
+ _skip(prot, map.valueType, recursionLimit - 1);
+ }
+ prot.readMapEnd();
+ break;
+
+ case TType.SET:
+ TSet set = prot.readSetBegin();
+ for (int i = 0; i < set.length; i++) {
+ _skip(prot, set.elementType, recursionLimit - 1);
+ }
+ prot.readSetEnd();
+ break;
+
+ case TType.LIST:
+ TList list = prot.readListBegin();
+ for (int i = 0; i < list.length; i++) {
+ _skip(prot, list.elementType, recursionLimit - 1);
+ }
+ prot.readListEnd();
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/lib/dart/lib/src/protocol/t_set.dart b/lib/dart/lib/src/protocol/t_set.dart
new file mode 100644
index 0000000..b342537
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_set.dart
@@ -0,0 +1,25 @@
+/// 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.
+
+part of thrift;
+
+class TSet {
+ final int elementType;
+ final int length;
+
+ TSet(this.elementType, this.length);
+}
diff --git a/lib/dart/lib/src/protocol/t_struct.dart b/lib/dart/lib/src/protocol/t_struct.dart
new file mode 100644
index 0000000..0303f63
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_struct.dart
@@ -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.
+
+part of thrift;
+
+class TStruct {
+ final String name;
+
+ TStruct([this.name = ""]);
+}
diff --git a/lib/dart/lib/src/protocol/t_type.dart b/lib/dart/lib/src/protocol/t_type.dart
new file mode 100644
index 0000000..3919d96
--- /dev/null
+++ b/lib/dart/lib/src/protocol/t_type.dart
@@ -0,0 +1,34 @@
+/// 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.
+
+part of thrift;
+
+class TType {
+ static const int STOP = 0;
+ static const int VOID = 1;
+ static const int BOOL = 2;
+ static const int BYTE = 3;
+ static const int DOUBLE = 4;
+ static const int I16 = 6;
+ static const int I32 = 8;
+ static const int I64 = 10;
+ static const int STRING = 11;
+ static const int STRUCT = 12;
+ static const int MAP = 13;
+ static const int SET = 14;
+ static const int LIST = 15;
+}
diff --git a/lib/dart/lib/src/t_application_error.dart b/lib/dart/lib/src/t_application_error.dart
new file mode 100644
index 0000000..6f8abd4
--- /dev/null
+++ b/lib/dart/lib/src/t_application_error.dart
@@ -0,0 +1,104 @@
+/// 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.
+
+part of thrift;
+
+class TApplicationErrorType {
+ static const int UNKNOWN = 0;
+ static const int UNKNOWN_METHOD = 1;
+ static const int INVALID_MESSAGE_TYPE = 2;
+ static const int WRONG_METHOD_NAME = 3;
+ static const int BAD_SEQUENCE_ID = 4;
+ static const int MISSING_RESULT = 5;
+ static const int INTERNAL_ERROR = 6;
+ static const int PROTOCOL_ERROR = 7;
+ static const int INVALID_TRANSFORM = 8;
+ static const int INVALID_PROTOCOL = 9;
+ static const int UNSUPPORTED_CLIENT_TYPE = 10;
+}
+
+class TApplicationError extends TError {
+ static final TStruct _struct = new TStruct("TApplicationError");
+ static const int MESSAGE = 1;
+ static final TField _messageField =
+ new TField("message", TType.STRING, MESSAGE);
+ static const int TYPE = 2;
+ static final TField _typeField = new TField("type", TType.I32, TYPE);
+
+ TApplicationError(
+ [int type = TApplicationErrorType.UNKNOWN, String message = ""])
+ : super(type, message);
+
+ static TApplicationError read(TProtocol iprot) {
+ TField field;
+
+ String message = null;
+ int type = TApplicationErrorType.UNKNOWN;
+
+ iprot.readStructBegin();
+ while (true) {
+ field = iprot.readFieldBegin();
+
+ if (field.type == TType.STOP) {
+ break;
+ }
+
+ switch (field.id) {
+ case MESSAGE:
+ if (field.type == TType.STRING) {
+ message = iprot.readString();
+ } else {
+ TProtocolUtil.skip(iprot, field.type);
+ }
+ break;
+
+ case TYPE:
+ if (field.type == TType.I32) {
+ type = iprot.readI32();
+ } else {
+ TProtocolUtil.skip(iprot, field.type);
+ }
+ break;
+
+ default:
+ TProtocolUtil.skip(iprot, field.type);
+ break;
+ }
+ iprot.readFieldEnd();
+ }
+ iprot.readStructEnd();
+
+ return new TApplicationError(type, message);
+ }
+
+ write(TProtocol oprot) {
+ oprot.writeStructBegin(_struct);
+
+ if (message != null && !message.isEmpty) {
+ oprot.writeFieldBegin(_messageField);
+ oprot.writeString(message);
+ oprot.writeFieldEnd();
+ }
+
+ oprot.writeFieldBegin(_typeField);
+ oprot.writeI32(type);
+ oprot.writeFieldEnd();
+
+ oprot.writeFieldStop();
+ oprot.writeStructEnd();
+ }
+}
diff --git a/lib/dart/lib/src/t_base.dart b/lib/dart/lib/src/t_base.dart
new file mode 100644
index 0000000..d5571b6
--- /dev/null
+++ b/lib/dart/lib/src/t_base.dart
@@ -0,0 +1,37 @@
+/// 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.
+
+part of thrift;
+
+abstract class TBase {
+ /// Reads the TObject from the given input protocol.
+ void read(TProtocol iprot);
+
+ /// Writes the objects out to the [oprot] protocol.
+ void write(TProtocol oprot);
+
+ /// Check if a field is currently set or unset, using the [fieldId].
+ bool isSet(int fieldId);
+
+ /// Get a field's value by [fieldId]. Primitive types will be wrapped in the
+ /// appropriate "boxed" types.
+ getFieldValue(int fieldId);
+
+ /// Set a field's value by [fieldId]. Primitive types must be "boxed" in the
+ /// appropriate object wrapper type.
+ setFieldValue(int fieldId, Object value);
+}
diff --git a/lib/dart/lib/src/t_error.dart b/lib/dart/lib/src/t_error.dart
new file mode 100644
index 0000000..93ab732
--- /dev/null
+++ b/lib/dart/lib/src/t_error.dart
@@ -0,0 +1,27 @@
+/// 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.
+
+part of thrift;
+
+class TError extends Error {
+ final String message;
+ final int type;
+
+ TError(this.type, this.message);
+
+ String toString() => "<TError type: $type message: '$message'>";
+}
diff --git a/lib/dart/lib/src/t_processor.dart b/lib/dart/lib/src/t_processor.dart
new file mode 100644
index 0000000..dcf20fb
--- /dev/null
+++ b/lib/dart/lib/src/t_processor.dart
@@ -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.
+
+part of thrift;
+
+/// A processor is a generic object which operates upon an input stream and
+/// writes to some output stream.
+abstract class TProcessor {
+ bool process(TProtocol input, TProtocol output);
+}
diff --git a/lib/dart/lib/src/transport/t_buffered_transport.dart b/lib/dart/lib/src/transport/t_buffered_transport.dart
new file mode 100644
index 0000000..1d44c62
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_buffered_transport.dart
@@ -0,0 +1,98 @@
+/// 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.
+
+part of thrift;
+
+/// Buffered implementation of [TTransport].
+class TBufferedTransport extends TTransport {
+ final List<int> _writeBuffer = [];
+ Iterator<int> _readIterator;
+
+ Uint8List _consumeWriteBuffer() {
+ Uint8List buffer = new Uint8List.fromList(_writeBuffer);
+ _writeBuffer.clear();
+ return buffer;
+ }
+
+ void _setReadBuffer(Uint8List readBuffer) {
+ _readIterator = readBuffer != null ? readBuffer.iterator : null;
+ }
+
+ void _reset({bool isOpen: false}) {
+ _isOpen = isOpen;
+ _writeBuffer.clear();
+ _readIterator = null;
+ }
+
+ bool get hasReadData => _readIterator != null;
+
+ bool _isOpen;
+ bool get isOpen => _isOpen;
+
+ Future open() async {
+ _reset(isOpen: true);
+ }
+
+ Future close() async {
+ _reset(isOpen: false);
+ }
+
+ int read(Uint8List buffer, int offset, int length) {
+ if (buffer == null) {
+ throw new ArgumentError.notNull("buffer");
+ }
+
+ if (offset + length > buffer.length) {
+ throw new ArgumentError("The range exceeds the buffer length");
+ }
+
+ if (_readIterator == null || length <= 0) {
+ return 0;
+ }
+
+ int i = 0;
+ while (i < length && _readIterator.moveNext()) {
+ buffer[offset + i] = _readIterator.current;
+ i++;
+ }
+
+ // cleanup iterator when we've reached the end
+ if (_readIterator.current == null) {
+ _readIterator = null;
+ }
+
+ return i;
+ }
+
+ void write(Uint8List buffer, int offset, int length) {
+ if (buffer == null) {
+ throw new ArgumentError.notNull("buffer");
+ }
+
+ if (offset + length > buffer.length) {
+ throw new ArgumentError("The range exceeds the buffer length");
+ }
+
+ _writeBuffer.addAll(buffer.sublist(offset, offset + length));
+ }
+
+ Future flush() {
+ _readIterator = _consumeWriteBuffer().iterator;
+
+ return new Future.value();
+ }
+}
diff --git a/lib/dart/lib/src/transport/t_framed_transport.dart b/lib/dart/lib/src/transport/t_framed_transport.dart
new file mode 100644
index 0000000..baf8f32
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_framed_transport.dart
@@ -0,0 +1,83 @@
+/// 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.
+
+part of thrift;
+
+/// Framed [TTransport].
+///
+/// Adapted from the Java Framed transport.
+class TFramedTransport extends TBufferedTransport {
+ static const int headerByteCount = 4;
+
+ final TTransport _transport;
+
+ final Uint8List headerBytes = new Uint8List(headerByteCount);
+
+ TFramedTransport(TTransport transport) : _transport = transport {
+ if (transport == null) {
+ throw new ArgumentError.notNull("transport");
+ }
+ }
+
+ bool get isOpen => _transport.isOpen;
+
+ Future open() {
+ _reset(isOpen: true);
+ return _transport.open();
+ }
+
+ Future close() {
+ _reset(isOpen: false);
+ return _transport.close();
+ }
+
+ int read(Uint8List buffer, int offset, int length) {
+ if (hasReadData) {
+ int got = super.read(buffer, offset, length);
+ if (got > 0) return got;
+ }
+
+ _readFrame();
+
+ return super.read(buffer, offset, length);
+ }
+
+ void _readFrame() {
+ _transport.readAll(headerBytes, 0, headerByteCount);
+ int size = headerBytes.buffer.asByteData().getUint32(0);
+
+ if (size < 0) {
+ throw new TTransportError(
+ TTransportErrorType.UNKNOWN, "Read a negative frame size: $size");
+ }
+
+ Uint8List buffer = new Uint8List(size);
+ _transport.readAll(buffer, 0, size);
+ _setReadBuffer(buffer);
+ }
+
+ Future flush() {
+ Uint8List buffer = _consumeWriteBuffer();
+ int length = buffer.length;
+
+ headerBytes.buffer.asByteData().setUint32(0, length);
+ _transport.write(headerBytes, 0, headerByteCount);
+ _transport.write(buffer, 0, length);
+
+ return _transport.flush();
+ }
+}
diff --git a/lib/dart/lib/src/transport/t_http_transport.dart b/lib/dart/lib/src/transport/t_http_transport.dart
new file mode 100644
index 0000000..0e99a1d
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_http_transport.dart
@@ -0,0 +1,100 @@
+/// 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.
+
+part of thrift;
+
+/// HTTP implementation of [TTransport].
+///
+/// For example:
+///
+/// var transport = new THttpClientTransport(new BrowserClient(),
+/// new THttpConfig(url, {'X-My-Custom-Header': 'my value'}));
+/// var protocol = new TJsonProtocol(transport);
+/// var client = new MyThriftServiceClient(protocol);
+/// var result = client.myMethod();
+///
+/// Adapted from the JS XHR HTTP transport.
+class THttpClientTransport extends TBufferedTransport {
+ final Client httpClient;
+ final THttpConfig config;
+
+ THttpClientTransport(this.httpClient, this.config) {
+ if (httpClient == null) {
+ throw new ArgumentError.notNull("httpClient");
+ }
+ }
+
+ Future close() async {
+ _reset(isOpen: false);
+ httpClient.close();
+ }
+
+ Future flush() {
+ var requestBody = CryptoUtils.bytesToBase64(_consumeWriteBuffer());
+
+ // Use a sync completer to ensure that the buffer can be read immediately
+ // after the read buffer is set, and avoid a race condition where another
+ // response could overwrite the read buffer.
+ var completer = new Completer.sync();
+
+ httpClient
+ .post(config.url, headers: config.headers, body: requestBody)
+ .then((response) {
+ Uint8List data;
+ try {
+ data = new Uint8List.fromList(
+ CryptoUtils.base64StringToBytes(response.body));
+ } on FormatException catch (_) {
+ throw new TProtocolError(TProtocolErrorType.INVALID_DATA,
+ "Expected a Base 64 encoded string.");
+ }
+
+ _setReadBuffer(data);
+ completer.complete();
+ });
+
+ return completer.future;
+ }
+}
+
+class THttpConfig {
+ final Uri url;
+
+ Map<String, String> _headers;
+ get headers => _headers;
+
+ THttpConfig(this.url, Map<String, String> headers) {
+ if (url == null || !url.hasAuthority) {
+ throw new ArgumentError("Invalid url");
+ }
+
+ _initHeaders(headers);
+ }
+
+ void _initHeaders(Map<String, String> initial) {
+ var h = {};
+
+ if (initial != null) {
+ h.addAll(initial);
+ }
+
+ h['Content-Type'] = 'application/x-thrift';
+ h['Accept'] = 'application/x-thrift';
+
+ _headers = new Map.unmodifiable(h);
+ }
+}
diff --git a/lib/dart/lib/src/transport/t_message_reader.dart b/lib/dart/lib/src/transport/t_message_reader.dart
new file mode 100644
index 0000000..8ca0708
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_message_reader.dart
@@ -0,0 +1,99 @@
+/// 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.
+
+part of thrift;
+
+/// [TMessageReader] extracts a [TMessage] from bytes. This is used to allow a
+/// transport to inspect the message seqid and map responses to requests.
+class TMessageReader {
+ final TProtocolFactory protocolFactory;
+
+ final int byteOffset;
+ final _TMessageReaderTransport _transport;
+
+ /// Construct a [MessageReader]. The optional [byteOffset] specifies the
+ /// number of bytes to skip before reading the [TMessage].
+ TMessageReader(this.protocolFactory, {int byteOffset: 0})
+ : _transport = new _TMessageReaderTransport(),
+ this.byteOffset = byteOffset;
+
+ TMessage readMessage(Uint8List bytes) {
+ _transport.reset(bytes, byteOffset);
+ TProtocol protocol = protocolFactory.getProtocol(_transport);
+ TMessage message = protocol.readMessageBegin();
+ _transport.reset(null);
+
+ return message;
+ }
+}
+
+/// An internal class used to support [TMessageReader].
+class _TMessageReaderTransport extends TTransport {
+ _TMessageReaderTransport();
+
+ Iterator<int> _readIterator;
+
+ void reset(Uint8List bytes, [int offset = 0]) {
+ if (bytes == null) {
+ _readIterator = null;
+ return;
+ }
+
+ if (offset > bytes.length) {
+ throw new ArgumentError("The offset exceeds the bytes length");
+ }
+
+ _readIterator = bytes.iterator;
+
+ for (var i = 0; i < offset; i++) {
+ _readIterator.moveNext();
+ }
+ }
+
+ get isOpen => true;
+
+ Future open() => throw new UnsupportedError("Unsupported in MessageReader");
+
+ Future close() => throw new UnsupportedError("Unsupported in MessageReader");
+
+ int read(Uint8List buffer, int offset, int length) {
+ if (buffer == null) {
+ throw new ArgumentError.notNull("buffer");
+ }
+
+ if (offset + length > buffer.length) {
+ throw new ArgumentError("The range exceeds the buffer length");
+ }
+
+ if (_readIterator == null || length <= 0) {
+ return 0;
+ }
+
+ int i = 0;
+ while (i < length && _readIterator.moveNext()) {
+ buffer[offset + i] = _readIterator.current;
+ i++;
+ }
+
+ return i;
+ }
+
+ void write(Uint8List buffer, int offset, int length) =>
+ throw new UnsupportedError("Unsupported in MessageReader");
+
+ Future flush() => throw new UnsupportedError("Unsupported in MessageReader");
+}
diff --git a/lib/dart/lib/src/transport/t_socket.dart b/lib/dart/lib/src/transport/t_socket.dart
new file mode 100644
index 0000000..74618b6
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket.dart
@@ -0,0 +1,38 @@
+/// 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.
+
+part of thrift;
+
+enum TSocketState { CLOSED, OPEN }
+
+abstract class TSocket {
+ Stream<TSocketState> get onState;
+
+ Stream<String> get onError;
+
+ Stream<Uint8List> get onMessage;
+
+ bool get isOpen;
+
+ bool get isClosed;
+
+ Future open();
+
+ Future close();
+
+ void send(Uint8List data);
+}
diff --git a/lib/dart/lib/src/transport/t_socket_transport.dart b/lib/dart/lib/src/transport/t_socket_transport.dart
new file mode 100644
index 0000000..ad7e48e
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_socket_transport.dart
@@ -0,0 +1,175 @@
+/// 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.
+
+part of thrift;
+
+/// Socket implementation of [TTransport].
+///
+/// For example:
+///
+/// var transport = new TClientSocketTransport(new TWebSocket(url));
+/// var protocol = new TBinaryProtocol(transport);
+/// var client = new MyThriftServiceClient(protocol);
+/// var result = client.myMethod();
+///
+/// Adapted from the JS WebSocket transport.
+abstract class TSocketTransport extends TBufferedTransport {
+ final Logger log = new Logger('thrift.TSocketTransport');
+
+ final TSocket socket;
+
+ /// A transport using the provided [socket].
+ TSocketTransport(this.socket) {
+ if (socket == null) {
+ throw new ArgumentError.notNull('socket');
+ }
+
+ socket.onError.listen((String e) => log.warning(e));
+ socket.onMessage.listen(handleIncomingMessage);
+ }
+
+ bool get isOpen => socket.isOpen;
+
+ Future open() {
+ _reset(isOpen: true);
+ return socket.open();
+ }
+
+ Future close() {
+ _reset(isOpen: false);
+ return socket.close();
+ }
+
+ /// Make an incoming message available to read from the transport.
+ void handleIncomingMessage(Uint8List messageBytes) {
+ _setReadBuffer(messageBytes);
+ }
+}
+
+/// [TClientSocketTransport] is a basic client socket transport. It sends
+/// outgoing messages and expects a response.
+///
+/// NOTE: This transport expects a single threaded server, as it will process
+/// responses in FIFO order.
+class TClientSocketTransport extends TSocketTransport {
+ final List<Completer<Uint8List>> _completers = [];
+
+ TClientSocketTransport(TSocket socket) : super(socket);
+
+ Future flush() {
+ Uint8List bytes = _consumeWriteBuffer();
+
+ // Use a sync completer to ensure that the buffer can be read immediately
+ // after the read buffer is set, and avoid a race condition where another
+ // response could overwrite the read buffer.
+ Completer completer = new Completer.sync();
+ _completers.add(completer);
+
+ socket.send(bytes);
+
+ return completer.future;
+ }
+
+ void handleIncomingMessage(Uint8List messageBytes) {
+ super.handleIncomingMessage(messageBytes);
+
+ if (_completers.isNotEmpty) {
+ var completer = _completers.removeAt(0);
+ completer.complete();
+ }
+ }
+}
+
+/// [TAsyncClientSocketTransport] sends outgoing messages and expects an
+/// asynchronous response.
+///
+/// NOTE: This transport uses a [MessageReader] to read a [TMessage] when an
+/// incoming message arrives to correlate a response to a request, using the
+/// seqid.
+class TAsyncClientSocketTransport extends TSocketTransport {
+ static const defaultTimeout = const Duration(seconds: 30);
+
+ final Map<int, Completer<Uint8List>> _completers = {};
+
+ final TMessageReader messageReader;
+
+ final Duration responseTimeout;
+
+ TAsyncClientSocketTransport(TSocket socket, TMessageReader messageReader,
+ {Duration responseTimeout: defaultTimeout})
+ : this.messageReader = messageReader,
+ this.responseTimeout = responseTimeout,
+ super(socket);
+
+ Future flush() {
+ Uint8List bytes = _consumeWriteBuffer();
+ TMessage message = messageReader.readMessage(bytes);
+ int seqid = message.seqid;
+
+ // Use a sync completer to ensure that the buffer can be read immediately
+ // after the read buffer is set, and avoid a race condition where another
+ // response could overwrite the read buffer.
+ Completer completer = new Completer.sync();
+ _completers[seqid] = completer;
+
+ if (responseTimeout != null) {
+ new Future.delayed(responseTimeout, () {
+ var completer = _completers.remove(seqid);
+ if (completer != null) {
+ completer.completeError(
+ new TimeoutException("Response timed out.", responseTimeout));
+ }
+ });
+ }
+
+ socket.send(bytes);
+
+ return completer.future;
+ }
+
+ void handleIncomingMessage(Uint8List messageBytes) {
+ super.handleIncomingMessage(messageBytes);
+
+ TMessage message = messageReader.readMessage(messageBytes);
+ var completer = _completers.remove(message.seqid);
+ if (completer != null) {
+ completer.complete();
+ }
+ }
+}
+
+/// [TServerSocketTransport] listens for incoming messages. When it sends a
+/// response, it does not expect an acknowledgement.
+class TServerSocketTransport extends TSocketTransport {
+ final StreamController _onIncomingMessageController;
+ Stream get onIncomingMessage => _onIncomingMessageController.stream;
+
+ TServerSocketTransport(TSocket socket)
+ : _onIncomingMessageController = new StreamController.broadcast(),
+ super(socket);
+
+ Future flush() async {
+ Uint8List message = _consumeWriteBuffer();
+ socket.send(message);
+ }
+
+ void handleIncomingMessage(Uint8List messageBytes) {
+ super.handleIncomingMessage(messageBytes);
+
+ _onIncomingMessageController.add(null);
+ }
+}
diff --git a/lib/dart/lib/src/transport/t_transport.dart b/lib/dart/lib/src/transport/t_transport.dart
new file mode 100644
index 0000000..563d5eb
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport.dart
@@ -0,0 +1,70 @@
+/// 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.
+
+part of thrift;
+
+abstract class TTransport {
+ /// Queries whether the transport is open.
+ /// Returns [true] if the transport is open.
+ bool get isOpen;
+
+ /// Opens the transport for reading/writing.
+ /// Throws [TTransportError] if the transport could not be opened.
+ Future open();
+
+ /// Closes the transport.
+ Future close();
+
+ /// Reads up to [length] bytes into [buffer], starting at [offset].
+ /// Returns the number of bytes actually read.
+ /// Throws [TTransportError] if there was an error reading data
+ int read(Uint8List buffer, int offset, int length);
+
+ /// Guarantees that all of [length] bytes are actually read off the transport.
+ /// Returns the number of bytes actually read, which must be equal to
+ /// [length].
+ /// Throws [TTransportError] if there was an error reading data
+ int readAll(Uint8List buffer, int offset, int length) {
+ int got = 0;
+ int ret = 0;
+ while (got < length) {
+ ret = read(buffer, offset + got, length - got);
+ if (ret <= 0) {
+ throw new TTransportError(
+ TTransportErrorType.UNKNOWN,
+ "Cannot read. Remote side has closed. Tried to read $length "
+ "bytes, but only got $got bytes.");
+ }
+ got += ret;
+ }
+ return got;
+ }
+
+ /// Writes up to [len] bytes from the buffer.
+ /// Throws [TTransportError] if there was an error writing data
+ void write(Uint8List buffer, int offset, int length);
+
+ /// Writes the [bytes] to the output.
+ /// Throws [TTransportError] if there was an error writing data
+ void writeAll(Uint8List buffer) {
+ write(buffer, 0, buffer.length);
+ }
+
+ /// Flush any pending data out of a transport buffer.
+ /// Throws [TTransportError] if there was an error writing out data.
+ Future flush();
+}
diff --git a/lib/dart/lib/src/transport/t_transport_error.dart b/lib/dart/lib/src/transport/t_transport_error.dart
new file mode 100644
index 0000000..d3508e0
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_error.dart
@@ -0,0 +1,31 @@
+/// 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.
+
+part of thrift;
+
+class TTransportErrorType {
+ static const int UNKNOWN = 0;
+ static const int NOT_OPEN = 1;
+ static const int ALREADY_OPEN = 2;
+ static const int TIMED_OUT = 3;
+ static const int END_OF_FILE = 4;
+}
+
+class TTransportError extends TError {
+ TTransportError([int type = TTransportErrorType.UNKNOWN, String message = ""])
+ : super(type, message);
+}
diff --git a/lib/dart/lib/src/transport/t_transport_factory.dart b/lib/dart/lib/src/transport/t_transport_factory.dart
new file mode 100644
index 0000000..7a10461
--- /dev/null
+++ b/lib/dart/lib/src/transport/t_transport_factory.dart
@@ -0,0 +1,27 @@
+/// 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.
+
+part of thrift;
+
+/// Factory class used to create wrapped instance of a [TTransport]. This is
+/// used primarily in servers.
+///
+/// Adapted from the Java version.
+class TTransportFactory {
+ Future<TTransport> getTransport(TTransport transport) =>
+ new Future.value(transport);
+}
diff --git a/lib/dart/lib/thrift.dart b/lib/dart/lib/thrift.dart
new file mode 100644
index 0000000..2483726
--- /dev/null
+++ b/lib/dart/lib/thrift.dart
@@ -0,0 +1,58 @@
+/// 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.
+
+library thrift;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show ByteData;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show Client;
+import 'package:logging/logging.dart';
+
+part 'src/t_application_error.dart';
+part 'src/t_base.dart';
+part 'src/t_error.dart';
+part 'src/t_processor.dart';
+
+part 'src/protocol/t_binary_protocol.dart';
+part 'src/protocol/t_field.dart';
+part 'src/protocol/t_json_protocol.dart';
+part 'src/protocol/t_list.dart';
+part 'src/protocol/t_map.dart';
+part 'src/protocol/t_message.dart';
+part 'src/protocol/t_multiplexed_protocol.dart';
+part 'src/protocol/t_protocol.dart';
+part 'src/protocol/t_protocol_decorator.dart';
+part 'src/protocol/t_protocol_error.dart';
+part 'src/protocol/t_protocol_factory.dart';
+part 'src/protocol/t_protocol_util.dart';
+part 'src/protocol/t_set.dart';
+part 'src/protocol/t_struct.dart';
+part 'src/protocol/t_type.dart';
+
+part 'src/transport/t_buffered_transport.dart';
+part 'src/transport/t_framed_transport.dart';
+part 'src/transport/t_http_transport.dart';
+part 'src/transport/t_message_reader.dart';
+part 'src/transport/t_socket.dart';
+part 'src/transport/t_transport.dart';
+part 'src/transport/t_transport_error.dart';
+part 'src/transport/t_transport_factory.dart';
+part 'src/transport/t_socket_transport.dart';
diff --git a/lib/dart/lib/thrift_browser.dart b/lib/dart/lib/thrift_browser.dart
new file mode 100644
index 0000000..2ebc257
--- /dev/null
+++ b/lib/dart/lib/thrift_browser.dart
@@ -0,0 +1,22 @@
+/// 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.
+
+library thrift_browser;
+
+/// Classes that are only supported in browser applications go here
+
+export 'src/browser/t_web_socket.dart' show TWebSocket;
diff --git a/lib/dart/lib/thrift_console.dart b/lib/dart/lib/thrift_console.dart
new file mode 100644
index 0000000..48a83d1
--- /dev/null
+++ b/lib/dart/lib/thrift_console.dart
@@ -0,0 +1,23 @@
+/// 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.
+
+library thrift_console;
+
+/// Classes that are only supported in console applications go here
+
+export 'src/console/t_tcp_socket.dart' show TTcpSocket;
+export 'src/console/t_web_socket.dart' show TWebSocket;
diff --git a/lib/dart/pubspec.yaml b/lib/dart/pubspec.yaml
new file mode 100644
index 0000000..f64d980
--- /dev/null
+++ b/lib/dart/pubspec.yaml
@@ -0,0 +1,36 @@
+# 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.
+
+name: thrift
+version: 1.0.0-dev
+description: >
+ A Dart library for Apache Thrift
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+documentation: https://github.com/apache/thrift
+environment:
+ sdk: ">=1.12.0 <2.0.0"
+dependencies:
+ crypto: "^0.9.0"
+ http: "^0.11.3"
+ logging: "^0.11.0"
+dev_dependencies:
+ coverage: "^0.7.2"
+ dart_dev: "^1.0.1"
+ dart_style: "^0.2.0"
+ mockito: "^0.11.0"
+ test: "^0.12.0"
diff --git a/lib/dart/test/protocol/t_protocol_test.dart b/lib/dart/test/protocol/t_protocol_test.dart
new file mode 100644
index 0000000..88ddd4f
--- /dev/null
+++ b/lib/dart/test/protocol/t_protocol_test.dart
@@ -0,0 +1,374 @@
+// 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.
+
+library thrift.test.transport.t_json_protocol_test;
+
+import 'dart:async';
+import 'dart:typed_data' show Uint8List;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+ final message = new TMessage('my message', TMessageType.ONEWAY, 123);
+
+ TProtocol protocol;
+
+ Primitive getPrimitive(int tType) {
+ switch (tType) {
+ case TType.BOOL:
+ return new Primitive(protocol.readBool, protocol.writeBool, false);
+
+ case TType.BYTE:
+ return new Primitive(protocol.readByte, protocol.writeByte, 0);
+
+ case TType.I16:
+ return new Primitive(protocol.readI16, protocol.writeI16, 0);
+
+ case TType.I32:
+ return new Primitive(protocol.readI32, protocol.writeI32, 0);
+
+ case TType.I64:
+ return new Primitive(protocol.readI64, protocol.writeI64, 0);
+
+ case TType.DOUBLE:
+ return new Primitive(protocol.readDouble, protocol.writeDouble, 0);
+
+ case TType.STRING:
+ return new Primitive(protocol.readString, protocol.writeString, '');
+
+ default:
+ throw new UnsupportedError("Unsupported TType $tType");
+ }
+ }
+
+ Future primitiveTest(Primitive primitive, input) async {
+ primitive.write(input);
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = primitive.read();
+
+ expect(output, input);
+ }
+
+ Future primitiveNullTest(Primitive primitive) async {
+ primitive.write(null);
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = primitive.read();
+
+ expect(output, primitive.defaultValue);
+ }
+
+ var sharedTests = () {
+ test('Test message', () async {
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ var subject = protocol.readMessageBegin();
+
+ expect(subject.name, message.name);
+ expect(subject.type, message.type);
+ expect(subject.seqid, message.seqid);
+ });
+
+ test('Test struct', () async {
+ var input = new TStruct();
+
+ protocol.writeStructBegin(input);
+ protocol.writeStructEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readStructBegin();
+
+ // name is not serialized, see C# version for reference
+ expect(output, isNotNull);
+ });
+
+ test('Test field', () async {
+ var input = new TField('my field', TType.MAP, 123);
+
+ protocol.writeFieldBegin(input);
+ protocol.writeFieldEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readFieldBegin();
+
+ // name is not serialized, see C# version for reference
+ expect(output.type, input.type);
+ expect(output.id, input.id);
+ });
+
+ test('Test map', () async {
+ var input = new TMap(TType.STRING, TType.STRUCT, 123);
+
+ protocol.writeMapBegin(input);
+ protocol.writeMapEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readMapBegin();
+
+ expect(output.keyType, input.keyType);
+ expect(output.valueType, input.valueType);
+ expect(output.length, input.length);
+ });
+
+ test('Test list', () async {
+ var input = new TList(TType.STRING, 123);
+
+ protocol.writeListBegin(input);
+ protocol.writeListEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readListBegin();
+
+ expect(output.elementType, input.elementType);
+ expect(output.length, input.length);
+ });
+
+ test('Test set', () async {
+ var input = new TSet(TType.STRING, 123);
+
+ protocol.writeSetBegin(input);
+ protocol.writeSetEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readListBegin();
+
+ expect(output.elementType, input.elementType);
+ expect(output.length, input.length);
+ });
+
+ test('Test bool', () async {
+ await primitiveTest(getPrimitive(TType.BOOL), true);
+ });
+
+ test('Test bool null', () async {
+ await primitiveNullTest(getPrimitive(TType.BOOL));
+ });
+
+ test('Test byte', () async {
+ await primitiveTest(getPrimitive(TType.BYTE), 64);
+ });
+
+ test('Test byte null', () async {
+ await primitiveNullTest(getPrimitive(TType.BYTE));
+ });
+
+ test('Test I16', () async {
+ await primitiveTest(getPrimitive(TType.I16), 32767);
+ });
+
+ test('Test I16 null', () async {
+ await primitiveNullTest(getPrimitive(TType.I16));
+ });
+
+ test('Test I32', () async {
+ await primitiveTest(getPrimitive(TType.I32), 2147483647);
+ });
+
+ test('Test I32 null', () async {
+ await primitiveNullTest(getPrimitive(TType.I32));
+ });
+
+ test('Test I64', () async {
+ await primitiveTest(getPrimitive(TType.I64), 9223372036854775807);
+ });
+
+ test('Test I64 null', () async {
+ await primitiveNullTest(getPrimitive(TType.I64));
+ });
+
+ test('Test double', () async {
+ await primitiveTest(getPrimitive(TType.DOUBLE), 3.1415926);
+ });
+
+ test('Test double null', () async {
+ await primitiveNullTest(getPrimitive(TType.DOUBLE));
+ });
+
+ test('Test string', () async {
+ var input = 'There are only two hard things in computer science: '
+ 'cache invalidation, naming things, and off-by-one errors.';
+ await primitiveTest(getPrimitive(TType.STRING), input);
+ });
+
+ test('Test string null', () async {
+ await primitiveNullTest(getPrimitive(TType.STRING));
+ });
+
+ test('Test binary', () async {
+ var input = new Uint8List.fromList(new List.filled(100, 123));
+
+ protocol.writeBinary(input);
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ var output = protocol.readBinary();
+
+ expect(output.length, input.length);
+ expect(output.every((i) => i == 123), isTrue);
+ });
+
+ test('Test complex struct', () async {
+ // {1: {10: 20}, 2: {30: 40}}
+ protocol.writeStructBegin(new TStruct());
+ protocol.writeFieldBegin(new TField('success', TType.MAP, 0));
+ protocol.writeMapBegin(new TMap(TType.I32, TType.MAP, 2));
+
+ protocol.writeI32(1); // key
+ protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+ protocol.writeI32(10); // key
+ protocol.writeI32(20); // value
+ protocol.writeMapEnd();
+
+ protocol.writeI32(2); // key
+ protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+ protocol.writeI32(30); // key
+ protocol.writeI32(40); // value
+ protocol.writeMapEnd();
+
+ protocol.writeMapEnd();
+ protocol.writeFieldEnd();
+ protocol.writeFieldStop();
+ protocol.writeStructEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ protocol.readStructBegin();
+ expect(protocol.readFieldBegin().type, TType.MAP);
+ expect(protocol.readMapBegin().length, 2);
+
+ expect(protocol.readI32(), 1); // key
+ expect(protocol.readMapBegin().length, 1);
+ expect(protocol.readI32(), 10); // key
+ expect(protocol.readI32(), 20); // value
+ protocol.readMapEnd();
+
+ expect(protocol.readI32(), 2); // key
+ expect(protocol.readMapBegin().length, 1);
+ expect(protocol.readI32(), 30); // key
+ expect(protocol.readI32(), 40); // value
+ protocol.readMapEnd();
+
+ protocol.readMapEnd();
+ protocol.readFieldEnd();
+ protocol.readStructEnd();
+ protocol.readMessageEnd();
+ });
+
+ test('Test nested maps and lists', () async {
+ // {1: [{10: 20}], 2: [{30: 40}]}
+ protocol.writeMapBegin(new TMap(TType.I32, TType.LIST, 2));
+
+ protocol.writeI32(1); // key
+ protocol.writeListBegin(new TList(TType.MAP, 1));
+ protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+ protocol.writeI32(10); // key
+ protocol.writeI32(20); // value
+ protocol.writeMapEnd();
+ protocol.writeListEnd();
+
+ protocol.writeI32(2); // key
+ protocol.writeListBegin(new TList(TType.MAP, 1));
+ protocol.writeMapBegin(new TMap(TType.I32, TType.I32, 1));
+ protocol.writeI32(30); // key
+ protocol.writeI32(40); // value
+ protocol.writeMapEnd();
+ protocol.writeListEnd();
+
+ protocol.writeMapEnd();
+ protocol.writeMessageEnd();
+
+ await protocol.transport.flush();
+
+ protocol.readMessageBegin();
+ expect(protocol.readMapBegin().length, 2);
+
+ expect(protocol.readI32(), 1); // key
+ expect(protocol.readListBegin().length, 1);
+ expect(protocol.readMapBegin().length, 1);
+ expect(protocol.readI32(), 10); // key
+ expect(protocol.readI32(), 20); // value
+ protocol.readMapEnd();
+ protocol.readListEnd();
+
+ expect(protocol.readI32(), 2); // key
+ expect(protocol.readListBegin().length, 1);
+ expect(protocol.readMapBegin().length, 1);
+ expect(protocol.readI32(), 30); // key
+ expect(protocol.readI32(), 40); // value
+ protocol.readMapEnd();
+ protocol.readListEnd();
+
+ protocol.readMapEnd();
+ protocol.readMessageEnd();
+ });
+ };
+
+ group('JSON', () {
+ setUp(() {
+ protocol = new TJsonProtocol(new TBufferedTransport());
+ protocol.writeMessageBegin(message);
+ });
+
+ group('shared tests', sharedTests);
+ });
+
+ group('binary', () {
+ setUp(() {
+ protocol = new TBinaryProtocol(new TBufferedTransport());
+ protocol.writeMessageBegin(message);
+ });
+
+ group('shared tests', sharedTests);
+ });
+}
+
+class Primitive {
+ final Function read;
+ final Function write;
+ final defaultValue;
+
+ Primitive(this.read, this.write, this.defaultValue);
+}
diff --git a/lib/dart/test/t_application_error_test.dart b/lib/dart/test/t_application_error_test.dart
new file mode 100644
index 0000000..511d8d6
--- /dev/null
+++ b/lib/dart/test/t_application_error_test.dart
@@ -0,0 +1,46 @@
+// 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.
+
+library thrift.test.t_application_error_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+ TProtocol protocol;
+
+ setUp(() {
+ protocol = new TBinaryProtocol(new TBufferedTransport());
+ });
+
+ test('Write and read an application error', () {
+ var expectedType = TApplicationErrorType.INTERNAL_ERROR;
+ var expectedMessage = 'test error message';
+
+ TApplicationError error =
+ new TApplicationError(expectedType, expectedMessage);
+ error.write(protocol);
+
+ protocol.transport.flush();
+
+ TApplicationError subject = TApplicationError.read(protocol);
+
+ expect(subject, isNotNull);
+ expect(subject.type, expectedType);
+ expect(subject.message, expectedMessage);
+ });
+}
diff --git a/lib/dart/test/transport/t_http_transport_test.dart b/lib/dart/test/transport/t_http_transport_test.dart
new file mode 100644
index 0000000..7fcab3e
--- /dev/null
+++ b/lib/dart/test/transport/t_http_transport_test.dart
@@ -0,0 +1,165 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Encoding;
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:http/http.dart' show BaseRequest;
+import 'package:http/http.dart' show Client;
+import 'package:http/http.dart' show Response;
+import 'package:http/http.dart' show StreamedResponse;
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+ const utf8Codec = const Utf8Codec();
+
+ group('THttpClientTransport', () {
+ FakeHttpClient client;
+ THttpClientTransport transport;
+
+ setUp(() {
+ client = new FakeHttpClient(sync: false);
+ var config = new THttpConfig(Uri.parse('http://localhost'), {});
+ transport = new THttpClientTransport(client, config);
+ });
+
+ test('Test transport sends body', () async {
+ var expectedText = 'my request';
+ transport.writeAll(utf8Codec.encode(expectedText));
+
+ expect(client.postRequest, isEmpty);
+
+ await transport.flush();
+
+ expect(client.postRequest, isNotEmpty);
+
+ var requestText =
+ utf8Codec.decode(CryptoUtils.base64StringToBytes(client.postRequest));
+ expect(requestText, expectedText);
+ });
+
+ test('Test transport receives response', () async {
+ var expectedText = 'my response';
+ var expectedBytes = utf8Codec.encode(expectedText);
+ client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+ transport.writeAll(utf8Codec.encode('my request'));
+ expect(transport.hasReadData, isFalse);
+
+ await transport.flush();
+
+ expect(transport.hasReadData, isTrue);
+
+ var buffer = new Uint8List(expectedBytes.length);
+ transport.readAll(buffer, 0, expectedBytes.length);
+
+ var bufferText = utf8Codec.decode(buffer);
+ expect(bufferText, expectedText);
+ });
+ });
+
+ group('THttpClientTransport with multiple messages', () {
+ FakeHttpClient client;
+ THttpClientTransport transport;
+
+ setUp(() {
+ client = new FakeHttpClient(sync: true);
+ var config = new THttpConfig(Uri.parse('http://localhost'), {});
+ transport = new THttpClientTransport(client, config);
+ });
+
+ test('Test read correct buffer after flush', () async {
+ String bufferText;
+ var expectedText = 'response 1';
+ var expectedBytes = utf8Codec.encode(expectedText);
+
+ // prepare a response
+ transport.writeAll(utf8Codec.encode('request 1'));
+ client.postResponse = CryptoUtils.bytesToBase64(expectedBytes);
+
+ Future responseReady = transport.flush().then((_) {
+ var buffer = new Uint8List(expectedBytes.length);
+ transport.readAll(buffer, 0, expectedBytes.length);
+ bufferText = utf8Codec.decode(buffer);
+ });
+
+ // prepare a second response
+ transport.writeAll(utf8Codec.encode('request 2'));
+ var response2Bytes = utf8Codec.encode('response 2');
+ client.postResponse = CryptoUtils.bytesToBase64(response2Bytes);
+ await transport.flush();
+
+ await responseReady;
+ expect(bufferText, expectedText);
+ });
+ });
+}
+
+class FakeHttpClient implements Client {
+ String postResponse = '';
+ String postRequest = '';
+
+ final bool sync;
+
+ FakeHttpClient({this.sync: false});
+
+ Future<Response> post(url,
+ {Map<String, String> headers, body, Encoding encoding}) {
+ postRequest = body;
+ var response = new Response(postResponse, 200);
+
+ if (sync) {
+ return new Future.sync(() => response);
+ } else {
+ return new Future.value(response);
+ }
+ }
+
+ Future<Response> head(url, {Map<String, String> headers}) =>
+ throw new UnimplementedError();
+
+ Future<Response> get(url, {Map<String, String> headers}) =>
+ throw new UnimplementedError();
+
+ Future<Response> put(url,
+ {Map<String, String> headers, body, Encoding encoding}) =>
+ throw new UnimplementedError();
+
+ Future<Response> patch(url,
+ {Map<String, String> headers, body, Encoding encoding}) =>
+ throw new UnimplementedError();
+
+ Future<Response> delete(url, {Map<String, String> headers}) =>
+ throw new UnimplementedError();
+
+ Future<String> read(url, {Map<String, String> headers}) =>
+ throw new UnimplementedError();
+
+ Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
+ throw new UnimplementedError();
+
+ Future<StreamedResponse> send(BaseRequest request) =>
+ throw new UnimplementedError();
+
+ void close() => throw new UnimplementedError();
+}
diff --git a/lib/dart/test/transport/t_socket_transport_test.dart b/lib/dart/test/transport/t_socket_transport_test.dart
new file mode 100644
index 0000000..997df0d
--- /dev/null
+++ b/lib/dart/test/transport/t_socket_transport_test.dart
@@ -0,0 +1,312 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'dart:async';
+import 'dart:convert' show Utf8Codec;
+import 'dart:typed_data' show Uint8List;
+
+import 'package:crypto/crypto.dart' show CryptoUtils;
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+void main() {
+ const utf8Codec = const Utf8Codec();
+
+ final requestText = 'my test request';
+ final requestBytes = new Uint8List.fromList(utf8Codec.encode(requestText));
+ final requestBase64 = CryptoUtils.bytesToBase64(requestBytes);
+
+ final responseText = 'response 1';
+ final responseBytes = new Uint8List.fromList(utf8Codec.encode(responseText));
+ final responseBase64 = CryptoUtils.bytesToBase64(responseBytes);
+
+ final framedResponseBase64 =
+ CryptoUtils.bytesToBase64(_getFramedResponse(responseBytes));
+
+ group('TClientSocketTransport', () {
+ FakeSocket socket;
+ TTransport transport;
+
+ setUp(() async {
+ socket = new FakeSocket(sync: false);
+ await socket.open();
+ transport = new TClientSocketTransport(socket);
+ await transport.open();
+ transport.writeAll(requestBytes);
+ });
+
+ test('Test client sending data over transport', () async {
+ expect(socket.sendPayload, isNull);
+
+ Future responseReady = transport.flush();
+
+ // allow microtask events to finish
+ await new Future.value();
+
+ expect(socket.sendPayload, isNotNull);
+ expect(socket.sendPayload, requestBytes);
+
+ // simulate a response
+ socket.receiveFakeMessage(responseBase64);
+
+ await responseReady;
+ var buffer = new Uint8List(responseBytes.length);
+ transport.readAll(buffer, 0, responseBytes.length);
+ var bufferText = utf8Codec.decode(buffer);
+
+ expect(bufferText, responseText);
+ });
+ }, timeout: new Timeout(new Duration(seconds: 1)));
+
+ group('TClientSocketTransport with FramedTransport', () {
+ FakeSocket socket;
+ TTransport transport;
+
+ setUp(() async {
+ socket = new FakeSocket(sync: true);
+ await socket.open();
+
+ transport = new TFramedTransport(new TClientSocketTransport(socket));
+ await transport.open();
+ transport.writeAll(requestBytes);
+ });
+
+ test('Test client sending data over framed transport', () async {
+ String bufferText;
+
+ Future responseReady = transport.flush().then((_) {
+ var buffer = new Uint8List(responseBytes.length);
+ transport.readAll(buffer, 0, responseBytes.length);
+ bufferText = utf8Codec.decode(buffer);
+ });
+
+ // simulate a response
+ socket.receiveFakeMessage(framedResponseBase64);
+
+ await responseReady;
+ expect(bufferText, responseText);
+ });
+ }, timeout: new Timeout(new Duration(seconds: 1)));
+
+ group('TAsyncClientSocketTransport', () {
+ FakeSocket socket;
+ FakeProtocolFactory protocolFactory;
+ TTransport transport;
+
+ setUp(() async {
+ socket = new FakeSocket(sync: true);
+ await socket.open();
+
+ protocolFactory = new FakeProtocolFactory();
+ protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+ transport = new TAsyncClientSocketTransport(
+ socket, new TMessageReader(protocolFactory),
+ responseTimeout: Duration.ZERO);
+ await transport.open();
+ transport.writeAll(requestBytes);
+ });
+
+ test('Test response correlates to correct request', () async {
+ String bufferText;
+
+ Future responseReady = transport.flush().then((_) {
+ var buffer = new Uint8List(responseBytes.length);
+ transport.readAll(buffer, 0, responseBytes.length);
+ bufferText = utf8Codec.decode(buffer);
+ });
+
+ // simulate a response
+ protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+ socket.receiveFakeMessage(responseBase64);
+
+ // simulate a second response
+ var response2Text = 'response 2';
+ var response2Bytes =
+ new Uint8List.fromList(utf8Codec.encode(response2Text));
+ var response2Base64 = CryptoUtils.bytesToBase64(response2Bytes);
+ protocolFactory.message = new TMessage('foo2', TMessageType.REPLY, 124);
+ socket.receiveFakeMessage(response2Base64);
+
+ await responseReady;
+ expect(bufferText, responseText);
+ });
+
+ test('Test response timeout', () async {
+ Future responseReady = transport.flush();
+ expect(responseReady, throwsA(new isInstanceOf<TimeoutException>()));
+ });
+ }, timeout: new Timeout(new Duration(seconds: 1)));
+
+ group('TAsyncClientSocketTransport with TFramedTransport', () {
+ FakeSocket socket;
+ FakeProtocolFactory protocolFactory;
+ TTransport transport;
+
+ setUp(() async {
+ socket = new FakeSocket(sync: true);
+ await socket.open();
+
+ protocolFactory = new FakeProtocolFactory();
+ protocolFactory.message = new TMessage('foo', TMessageType.CALL, 123);
+ var messageReader = new TMessageReader(protocolFactory,
+ byteOffset: TFramedTransport.headerByteCount);
+
+ transport = new TFramedTransport(new TAsyncClientSocketTransport(
+ socket, messageReader,
+ responseTimeout: Duration.ZERO));
+ await transport.open();
+ transport.writeAll(requestBytes);
+ });
+
+ test('Test async client sending data over framed transport', () async {
+ String bufferText;
+
+ Future responseReady = transport.flush().then((_) {
+ var buffer = new Uint8List(responseBytes.length);
+ transport.readAll(buffer, 0, responseBytes.length);
+ bufferText = utf8Codec.decode(buffer);
+ });
+
+ // simulate a response
+ protocolFactory.message = new TMessage('foo', TMessageType.REPLY, 123);
+ socket.receiveFakeMessage(framedResponseBase64);
+
+ await responseReady;
+ expect(bufferText, responseText);
+ });
+ }, timeout: new Timeout(new Duration(seconds: 1)));
+
+ group('TServerTransport', () {
+ test('Test server transport listens to socket', () async {
+ var socket = new FakeSocket();
+ await socket.open();
+ expect(socket.isOpen, isTrue);
+
+ var transport = new TServerSocketTransport(socket);
+ expect(transport.hasReadData, isFalse);
+
+ socket.receiveFakeMessage(requestBase64);
+
+ // allow microtask events to finish
+ await new Future.value();
+
+ expect(transport.hasReadData, isTrue);
+
+ var buffer = new Uint8List(requestBytes.length);
+ transport.readAll(buffer, 0, requestBytes.length);
+
+ var bufferText = utf8Codec.decode(buffer);
+ expect(bufferText, requestText);
+ });
+
+ test('Test server sending data over transport', () async {
+ var socket = new FakeSocket();
+ await socket.open();
+
+ var transport = new TServerSocketTransport(socket);
+
+ transport.writeAll(responseBytes);
+ expect(socket.sendPayload, isNull);
+
+ transport.flush();
+
+ // allow microtask events to finish
+ await new Future.value();
+
+ expect(socket.sendPayload, isNotNull);
+ expect(socket.sendPayload, responseBytes);
+ });
+ }, timeout: new Timeout(new Duration(seconds: 1)));
+}
+
+class FakeSocket extends TSocket {
+ final StreamController<TSocketState> _onStateController;
+ Stream<TSocketState> get onState => _onStateController.stream;
+
+ final StreamController<Object> _onErrorController;
+ Stream<Object> get onError => _onErrorController.stream;
+
+ final StreamController<Uint8List> _onMessageController;
+ Stream<Uint8List> get onMessage => _onMessageController.stream;
+
+ FakeSocket({bool sync: false})
+ : _onStateController = new StreamController.broadcast(sync: sync),
+ _onErrorController = new StreamController.broadcast(sync: sync),
+ _onMessageController = new StreamController.broadcast(sync: sync);
+
+ bool _isOpen;
+
+ bool get isOpen => _isOpen;
+
+ bool get isClosed => !isOpen;
+
+ Future open() async {
+ _isOpen = true;
+ _onStateController.add(TSocketState.OPEN);
+ }
+
+ Future close() async {
+ _isOpen = false;
+ _onStateController.add(TSocketState.CLOSED);
+ }
+
+ Uint8List _sendPayload;
+ Uint8List get sendPayload => _sendPayload;
+
+ void send(Uint8List data) {
+ if (!isOpen) throw new StateError('The socket is not open');
+
+ _sendPayload = data;
+ }
+
+ void receiveFakeMessage(String base64) {
+ if (!isOpen) throw new StateError('The socket is not open');
+
+ var message =
+ new Uint8List.fromList(CryptoUtils.base64StringToBytes(base64));
+ _onMessageController.add(message);
+ }
+}
+
+class FakeProtocolFactory implements TProtocolFactory {
+ FakeProtocolFactory();
+
+ TMessage message;
+
+ getProtocol(TTransport transport) => new FakeProtocol(message);
+}
+
+class FakeProtocol extends Mock implements TProtocol {
+ FakeProtocol(this._message);
+
+ TMessage _message;
+
+ readMessageBegin() => _message;
+}
+
+Uint8List _getFramedResponse(Uint8List responseBytes) {
+ var byteOffset = TFramedTransport.headerByteCount;
+ var response = new Uint8List(byteOffset + responseBytes.length);
+
+ response.buffer.asByteData().setInt32(0, responseBytes.length);
+ response.setAll(byteOffset, responseBytes);
+
+ return response;
+}
diff --git a/lib/dart/test/transport/t_transport_test.dart b/lib/dart/test/transport/t_transport_test.dart
new file mode 100644
index 0000000..0bb381a
--- /dev/null
+++ b/lib/dart/test/transport/t_transport_test.dart
@@ -0,0 +1,41 @@
+// 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.
+
+library thrift.test.transport.t_socket_transport_test;
+
+import 'package:test/test.dart';
+import 'package:thrift/thrift.dart';
+
+/// Common transport tests
+void main() {
+ group('TTransportFactory', () {
+ test('transport is returned from base factory', () async {
+ TTransport result;
+ TTransport transport = null;
+
+ var factory = new TTransportFactory();
+
+ result = await factory.getTransport(transport);
+ expect(result, isNull);
+
+ transport = new TBufferedTransport();
+ result = await factory.getTransport(transport);
+
+ expect(result, transport);
+ });
+ });
+}
diff --git a/lib/dart/tool/dev.dart b/lib/dart/tool/dev.dart
new file mode 100644
index 0000000..27f8b8f
--- /dev/null
+++ b/lib/dart/tool/dev.dart
@@ -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.
+
+library tool.dev;
+
+import 'package:dart_dev/dart_dev.dart' show dev, config;
+
+main(List<String> args) async {
+ // https://github.com/Workiva/dart_dev
+
+ var directories = ['lib/', 'test/', 'tool/'];
+ config.analyze.entryPoints = directories;
+ config.format.directories = directories;
+ config.copyLicense
+ ..licensePath = 'LICENSE_HEADER'
+ ..directories = directories;
+
+ await dev(args);
+}
diff --git a/test/Makefile.am b/test/Makefile.am
index 6ebcd27..7590921 100755
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -40,6 +40,11 @@
PRECROSS_TARGET += precross-php
endif
+if WITH_DART
+SUBDIRS += dart
+PRECROSS_TARGET += precross-dart
+endif
+
if WITH_PYTHON
SUBDIRS += py
PRECROSS_TARGET += precross-py
diff --git a/test/dart/Makefile.am b/test/dart/Makefile.am
new file mode 100644
index 0000000..59b3b7d
--- /dev/null
+++ b/test/dart/Makefile.am
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+gen-dart/thrift_test/lib/thrift_test.dart: ../ThriftTest.thrift
+ $(THRIFT) --gen dart ../ThriftTest.thrift
+
+pub-get-gen: gen-dart/thrift_test/lib/thrift_test.dart
+ cd gen-dart/thrift_test; ${DARTPUB} get
+
+pub-get: pub-get-gen
+ cd test_client; ${DARTPUB} get
+
+stubs: gen-dart/thrift_test/lib/thrift_test.dart pub-get
+
+precross: stubs
+
+check: stubs
+
+clean-local:
+ $(RM) -r gen-dart test_client/.pub
+
+client: stubs
+ ${DART} test_client/bin/main.dart
diff --git a/test/dart/test_client/bin/main.dart b/test/dart/test_client/bin/main.dart
new file mode 100644
index 0000000..3733a08
--- /dev/null
+++ b/test/dart/test_client/bin/main.dart
@@ -0,0 +1,288 @@
+/// 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:collection/equality.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:thrift_test/thrift_test.dart';
+
+ThriftTestClient client;
+bool verbose;
+
+/// Adapted from TestClient.php
+main(List<String> args) async {
+ var parser = new ArgParser();
+ parser.addOption('host', defaultsTo: 'localhost', help: 'The server host');
+ parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to');
+ parser.addOption('transport',
+ defaultsTo: 'buffered',
+ allowed: ['buffered', 'framed'],
+ help: 'The transport name',
+ allowedHelp: {
+ 'buffered': 'TBufferedTransport',
+ 'framed': 'TFramedTransport'
+ });
+ parser.addOption('protocol',
+ defaultsTo: 'binary',
+ allowed: ['binary', 'json'],
+ help: 'The protocol name',
+ allowedHelp: {'binary': 'TBinaryProtocol', 'json': 'TJsonProtocol'});
+ parser.addFlag('verbose', defaultsTo: false);
+
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+ } catch (e) {
+ stdout.writeln('$e\n');
+ results = null;
+ }
+
+ if (results == null) {
+ print(parser.usage);
+ exit(0);
+ }
+
+ verbose = results['verbose'] == true;
+
+ await init(
+ host: results['host'],
+ port: int.parse(results['port']),
+ transportType: results['transport'],
+ protocolType: results['protocol']).then((_) {
+ exit(0);
+ }).catchError((e) {
+ stdout.writeln('Error:');
+ stdout.writeln('$e');
+ if (e is Error) {
+ stdout.writeln('${e.stackTrace}');
+ }
+ exit(1);
+ });
+ exit(0);
+}
+
+TProtocolFactory getProtocolFactory(String protocolType) {
+ if (protocolType == 'binary') {
+ return new TBinaryProtocolFactory();
+ } else if (protocolType == 'json') {
+ return new TJsonProtocolFactory();
+ }
+
+ throw new ArgumentError.value(protocolType);
+}
+
+Future init(
+ {String host, int port, String transportType, String protocolType}) async {
+ TTransport transport;
+ var protocolFactory = getProtocolFactory(protocolType);
+
+ if (transportType == 'http') {
+ var httpClient = new HttpClient();
+ var config = new THttpConfig(Uri.parse('http://localhost'), {});
+ transport = new THttpClientTransport(httpClient, config);
+ } else {
+ var socket = await Socket.connect(host, port);
+ transport = new TClientSocketTransport(new TTcpSocket(socket));
+ if (transportType == 'framed') {
+ transport = new TFramedTransport(transport);
+ }
+ }
+
+ var protocol = protocolFactory.getProtocol(transport);
+ client = new ThriftTestClient(protocol);
+
+ await transport.open();
+
+ await runTests();
+}
+
+Future _test(String name, Function testFunc) async {
+ if (verbose) stdout.write('$name... ');
+ await testFunc();
+ if (verbose) stdout.writeln('success!');
+}
+
+Future runTests() async {
+ var xtruct = new Xtruct()
+ ..string_thing = 'Zero'
+ ..byte_thing = 1
+ ..i32_thing = -3
+ ..i64_thing = -5;
+
+ await _test('testVoid', () async {
+ await client.testVoid();
+ });
+
+ await _test('testString', () async {
+ var input = 'Test';
+ var result = await client.testString(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testBool', () async {
+ var input = true;
+ var result = await client.testBool(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testByte', () async {
+ var input = 64;
+ var result = await client.testByte(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testI32', () async {
+ var input = 2147483647;
+ var result = await client.testI32(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testI64', () async {
+ var input = 9223372036854775807;
+ var result = await client.testI64(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testDouble', () async {
+ var input = 3.1415926;
+ var result = await client.testDouble(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testBinary', () async {
+ var utf8Codec = const Utf8Codec();
+ var input = utf8Codec.encode('foo');
+ var result = await client.testBinary(input);
+ var equality = const ListEquality();
+ if (!equality.equals(
+ result, input)) throw new StateError('$result != $input');
+ });
+
+ await _test('testStruct', () async {
+ var result = await client.testStruct(xtruct);
+ if ('$result' != '$xtruct') throw new StateError('$result != $xtruct');
+ });
+
+ await _test('testNest', () async {
+ var input = new Xtruct2()
+ ..byte_thing = 1
+ ..struct_thing = xtruct
+ ..i32_thing = -3;
+
+ var result = await client.testNest(input);
+ if ('$result' != '$input') throw new StateError('$result != $input');
+ });
+
+ await _test('testMap', () async {
+ Map<int, int> input = {1: -10, 2: -9, 3: -8, 4: -7, 5: -6};
+
+ var result = await client.testMap(input);
+ var equality = const MapEquality();
+ if (!equality.equals(
+ result, input)) throw new StateError('$result != $input');
+ });
+
+ await _test('testSet', () async {
+ var input = new Set.from([-2, -1, 0, 1, 2]);
+ var result = await client.testSet(input);
+ var equality = const SetEquality();
+ if (!equality.equals(
+ result, input)) throw new StateError('$result != $input');
+ });
+
+ await _test('testList', () async {
+ var input = [-2, -1, 0, 1, 2];
+ var result = await client.testList(input);
+ var equality = const ListEquality();
+ if (!equality.equals(
+ result, input)) throw new StateError('$result != $input');
+ });
+
+ await _test('testEnum', () async {
+ await _testEnum(Numberz.ONE);
+ await _testEnum(Numberz.TWO);
+ await _testEnum(Numberz.THREE);
+ await _testEnum(Numberz.FIVE);
+ await _testEnum(Numberz.EIGHT);
+ });
+
+ await _test('testTypedef', () async {
+ var input = 309858235082523;
+ var result = await client.testTypedef(input);
+ if (result != input) throw new StateError('$result != $input');
+ });
+
+ await _test('testMapMap', () async {
+ Map<int, Map<int, int>> result = await client.testMapMap(1);
+ if (result.isEmpty ||
+ result[result.keys.first].isEmpty) throw new StateError(
+ 'expected Map<int, Map<int, int>> got $result');
+ });
+
+ await _test('testInsanity', () async {
+ var input = new Insanity();
+ input.userMap = {Numberz.FIVE: 5000};
+ input.xtructs = [xtruct];
+
+ Map<int, Map<int, Insanity>> result = await client.testInsanity(input);
+ if (result.isEmpty ||
+ result[result.keys.first].isEmpty) throw new StateError(
+ 'expected Map<int, Map<int, Insanity>> got $result');
+ });
+
+ await _test('testMulti', () async {
+ var input = new Xtruct()
+ ..string_thing = 'Hello2'
+ ..byte_thing = 123
+ ..i32_thing = 456
+ ..i64_thing = 789;
+
+ var result = await client.testMulti(input.byte_thing, input.i32_thing,
+ input.i64_thing, {1: 'one'}, Numberz.EIGHT, 5678);
+ if ('$result' != '$input') throw new StateError('$result != $input');
+ });
+
+ await _test('testException', () async {
+ try {
+ await client.testException('Xception');
+ } on Xception catch (x) {
+ return;
+ }
+
+ throw new StateError('expected an Xception');
+ });
+
+ await _test('testMultiException', () async {
+ try {
+ await client.testMultiException('Xception2', 'foo');
+ } on Xception2 catch (x) {
+ return;
+ }
+
+ throw new StateError('expected an Xception2');
+ });
+}
+
+Future _testEnum(int input) async {
+ var result = await client.testEnum(input);
+ if (result != input) throw new StateError('$result != $input');
+}
diff --git a/test/dart/test_client/pubspec.yaml b/test/dart/test_client/pubspec.yaml
new file mode 100644
index 0000000..54d8b0f
--- /dev/null
+++ b/test/dart/test_client/pubspec.yaml
@@ -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.
+
+name: thrift_test_client
+version: 1.0.0-dev
+description: A client integration test for the Dart Thrift library
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+ sdk: ^1.12.0
+dependencies:
+ args: ^0.13.0
+ thrift:
+ path: ../../../lib/dart
+ thrift_test:
+ path: ../gen-dart/thrift_test
+dev_dependencies:
+ test: "^0.12.0"
diff --git a/test/tests.json b/test/tests.json
index c902cd4..8c152fc 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -354,5 +354,27 @@
]
},
"workdir": "php"
+ },
+ {
+ "name": "dart",
+ "client": {
+ "transports": [
+ "buffered",
+ "framed",
+ "http"
+ ],
+ "sockets": [
+ "ip"
+ ],
+ "protocols": [
+ "binary",
+ "json"
+ ],
+ "command": [
+ "dart",
+ "test_client/bin/main.dart"
+ ]
+ },
+ "workdir": "dart"
}
]
diff --git a/tutorial/Makefile.am b/tutorial/Makefile.am
index 47711a9..37addda 100755
--- a/tutorial/Makefile.am
+++ b/tutorial/Makefile.am
@@ -62,6 +62,10 @@
SUBDIRS += nodejs
endif
+if WITH_DART
+SUBDIRS += dart
+endif
+
#
# generate html for ThriftTest.thrift
#
diff --git a/tutorial/dart/Makefile.am b/tutorial/dart/Makefile.am
new file mode 100644
index 0000000..2bb6bf2
--- /dev/null
+++ b/tutorial/dart/Makefile.am
@@ -0,0 +1,56 @@
+#
+# 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.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+gen-dart/tutorial/lib/tutorial.dart gen-dart/shared/lib/shared.dart: $(top_srcdir)/tutorial/tutorial.thrift
+ $(THRIFT) --gen dart -r $<
+
+all-local: gen-dart/tutorial/lib/tutorial.dart pub-get
+
+clean-local:
+ $(RM) -r gen-*
+
+pub-get: pub-get-gen pub-get-client pub-get-console-client pub-get-server
+
+pub-get-gen: pub-get-tutorial pub-get-shared
+
+pub-get-tutorial: gen-dart/tutorial/lib/tutorial.dart
+ cd gen-dart/tutorial; ${DARTPUB} get
+
+pub-get-shared: gen-dart/shared/lib/shared.dart
+ cd gen-dart/shared; ${DARTPUB} get
+
+pub-get-client:
+ cd client; ${DARTPUB} get
+
+pub-get-console-client:
+ cd console_client; ${DARTPUB} get
+
+pub-get-server:
+ cd client; ${DARTPUB} get
+
+tutorialserver: pub-get-gen pub-get-server
+ ${DART} server/bin/main.dart
+
+tutorialclient: pub-get-gen pub-get-client
+ cd client; ${DARTPUB} serve
+
+tutorialconsoleclient: pub-get-console-client
+ ${DART} console-client/bin/main.dart
diff --git a/tutorial/dart/build.sh b/tutorial/dart/build.sh
new file mode 100644
index 0000000..eabe04a
--- /dev/null
+++ b/tutorial/dart/build.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# 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.
+
+set -e;
+rm -r gen-dart || true;
+
+thrift --gen dart ../shared.thrift;
+cd gen-dart/shared;
+pub get;
+cd ../..;
+
+thrift --gen dart ../tutorial.thrift;
+cd gen-dart/tutorial;
+pub get;
+cd ../..;
+
+cd client;
+pub get;
+cd ..;
+
+cd console_client;
+pub get;
+cd ..;
+
+cd server;
+pub get;
+cd ..;
+
+dartfmt -w gen-dart;
+
+echo "\nEnjoy the Dart tutorial!";
+echo "\nTo run the server:";
+echo "> dart server/bin/main.dart";
+echo "\nTo run the client:";
+echo "# Serve the app from the client directory and view in a browser";
+echo "> cd client;";
+echo "> pub serve;";
+echo "\nTo run the console client:";
+echo "> dart console_client/bin/main.dart";
+echo "";
diff --git a/tutorial/dart/client/pubspec.yaml b/tutorial/dart/client/pubspec.yaml
new file mode 100644
index 0000000..97c625b
--- /dev/null
+++ b/tutorial/dart/client/pubspec.yaml
@@ -0,0 +1,34 @@
+# 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.
+
+name: tutorial_client
+version: 1.0.0-dev
+description: A Dart client implementation of the Apache Thrift tutorial
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+
+environment:
+ sdk: ^1.12.0
+
+dependencies:
+ browser: ^0.10.0
+ shared:
+ path: ../gen-dart/shared
+ thrift:
+ path: ../../../lib/dart
+ tutorial:
+ path: ../gen-dart/tutorial
diff --git a/tutorial/dart/client/web/client.dart b/tutorial/dart/client/web/client.dart
new file mode 100644
index 0000000..4f02d0d
--- /dev/null
+++ b/tutorial/dart/client/web/client.dart
@@ -0,0 +1,278 @@
+/// 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.
+
+import 'dart:html';
+
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_browser.dart';
+import 'package:shared/shared.dart';
+import 'package:tutorial/tutorial.dart';
+
+/// Adapted from the AS3 tutorial
+void main() {
+ new CalculatorUI(querySelector('#output')).start();
+}
+
+class CalculatorUI {
+ final DivElement output;
+
+ CalculatorUI(this.output);
+
+ TTransport _transport;
+ Calculator _calculatorClient;
+
+ void start() {
+ _buildInterface();
+ _initConnection();
+ }
+
+ void _validate() {
+ if (!_transport.isOpen) {
+ window.alert("The transport is not open!");
+ }
+ }
+
+ void _initConnection() {
+ _transport = new TAsyncClientSocketTransport(
+ new TWebSocket(Uri.parse('ws://127.0.0.1:9090/ws')),
+ new TMessageReader(new TBinaryProtocolFactory()));
+ TProtocol protocol = new TBinaryProtocol(_transport);
+ _transport.open();
+
+ _calculatorClient = new CalculatorClient(protocol);
+ }
+
+ void _buildInterface() {
+ output.children.forEach((e) {
+ e.remove();
+ });
+
+ _buildPingComponent();
+
+ _buildAddComponent();
+
+ _buildCalculatorComponent();
+
+ _buildGetStructComponent();
+ }
+
+ void _buildPingComponent() {
+ output.append(new HeadingElement.h3()..text = "Ping");
+ ButtonElement pingButton = new ButtonElement()
+ ..text = "PING"
+ ..onClick.listen(_onPingClick);
+ output.append(pingButton);
+ }
+
+ void _onPingClick(MouseEvent e) {
+ _validate();
+
+ _calculatorClient.ping();
+ }
+
+ void _buildAddComponent() {
+ output.append(new HeadingElement.h3()..text = "Add");
+ InputElement num1 = new InputElement()
+ ..id = "add1"
+ ..type = "number"
+ ..style.fontSize = "14px"
+ ..style.width = "50px";
+ output.append(num1);
+ SpanElement op = new SpanElement()
+ ..text = "+"
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px";
+ output.append(op);
+ InputElement num2 = new InputElement()
+ ..id = "add2"
+ ..type = "number"
+ ..style.fontSize = "14px"
+ ..style.width = "50px"
+ ..style.marginLeft = "10px";
+ output.append(num2);
+ ButtonElement addButton = new ButtonElement()
+ ..text = "="
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px"
+ ..onClick.listen(_onAddClick);
+ output.append(addButton);
+ SpanElement result = new SpanElement()
+ ..id = "addResult"
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px";
+ output.append(result);
+ }
+
+ void _onAddClick(MouseEvent e) {
+ _validate();
+
+ InputElement num1 = querySelector("#add1");
+ InputElement num2 = querySelector("#add2");
+ SpanElement result = querySelector("#addResult");
+
+ _calculatorClient
+ .add(int.parse(num1.value), int.parse(num2.value))
+ .then((int n) {
+ result.text = "$n";
+ });
+ }
+
+ void _buildCalculatorComponent() {
+ output.append(new HeadingElement.h3()..text = "Calculator");
+ InputElement num1 = new InputElement()
+ ..id = "calc1"
+ ..type = "number"
+ ..style.fontSize = "14px"
+ ..style.width = "50px";
+ output.append(num1);
+ SelectElement op = new SelectElement()
+ ..id = "calcOp"
+ ..multiple = false
+ ..selectedIndex = 0
+ ..style.fontSize = "16px"
+ ..style.marginLeft = "10px"
+ ..style.width = "50px";
+ OptionElement addOp = new OptionElement()
+ ..text = "+"
+ ..value = Operation.ADD.toString();
+ op.add(addOp, 0);
+ OptionElement subtractOp = new OptionElement()
+ ..text = "-"
+ ..value = Operation.SUBTRACT.toString();
+ op.add(subtractOp, 1);
+ OptionElement multiplyOp = new OptionElement()
+ ..text = "*"
+ ..value = Operation.MULTIPLY.toString();
+ op.add(multiplyOp, 2);
+ OptionElement divideOp = new OptionElement()
+ ..text = "/"
+ ..value = Operation.DIVIDE.toString();
+ op.add(divideOp, 3);
+ output.append(op);
+ InputElement num2 = new InputElement()
+ ..id = "calc2"
+ ..type = "number"
+ ..style.fontSize = "14px"
+ ..style.width = "50px"
+ ..style.marginLeft = "10px";
+ output.append(num2);
+ ButtonElement calcButton = new ButtonElement()
+ ..text = "="
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px"
+ ..onClick.listen(_onCalcClick);
+ output.append(calcButton);
+ SpanElement result = new SpanElement()
+ ..id = "calcResult"
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px";
+ output.append(result);
+ output.append(new BRElement());
+ output.append(new BRElement());
+ LabelElement logIdLabel = new LabelElement()
+ ..text = "Log ID:"
+ ..style.fontSize = "14px";
+ output.append(logIdLabel);
+ InputElement logId = new InputElement()
+ ..id = "logId"
+ ..type = "number"
+ ..value = "1"
+ ..style.fontSize = "14px"
+ ..style.width = "50px"
+ ..style.marginLeft = "10px";
+ output.append(logId);
+ LabelElement commentLabel = new LabelElement()
+ ..text = "Comment:"
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px";
+ output.append(commentLabel);
+ InputElement comment = new InputElement()
+ ..id = "comment"
+ ..style.fontSize = "14px"
+ ..style.width = "100px"
+ ..style.marginLeft = "10px";
+ output.append(comment);
+ }
+
+ void _onCalcClick(MouseEvent e) {
+ _validate();
+
+ InputElement num1 = querySelector("#calc1");
+ InputElement num2 = querySelector("#calc2");
+ SelectElement op = querySelector("#calcOp");
+ SpanElement result = querySelector("#calcResult");
+ InputElement logId = querySelector("#logId");
+ InputElement comment = querySelector("#comment");
+
+ int logIdValue = int.parse(logId.value);
+ logId.value = (logIdValue + 1).toString();
+
+ Work work = new Work();
+ work.num1 = int.parse(num1.value);
+ work.num2 = int.parse(num2.value);
+ work.op = int.parse(op.options[op.selectedIndex].value);
+ work.comment = comment.value;
+
+ _calculatorClient.calculate(logIdValue, work).then((int n) {
+ result.text = "$n";
+ });
+ }
+
+ void _buildGetStructComponent() {
+ output.append(new HeadingElement.h3()..text = "Get Struct");
+ LabelElement logIdLabel = new LabelElement()
+ ..text = "Struct Key:"
+ ..style.fontSize = "14px";
+ output.append(logIdLabel);
+ InputElement logId = new InputElement()
+ ..id = "structKey"
+ ..type = "number"
+ ..value = "1"
+ ..style.fontSize = "14px"
+ ..style.width = "50px"
+ ..style.marginLeft = "10px";
+ output.append(logId);
+ ButtonElement getStructButton = new ButtonElement()
+ ..text = "GET"
+ ..style.fontSize = "14px"
+ ..style.marginLeft = "10px"
+ ..onClick.listen(_onGetStructClick);
+ output.append(getStructButton);
+ output.append(new BRElement());
+ output.append(new BRElement());
+ TextAreaElement result = new TextAreaElement()
+ ..id = "getStructResult"
+ ..style.fontSize = "14px"
+ ..style.width = "300px"
+ ..style.height = "50px"
+ ..style.marginLeft = "10px";
+ output.append(result);
+ }
+
+ void _onGetStructClick(MouseEvent e) {
+ _validate();
+
+ InputElement structKey = querySelector("#structKey");
+ TextAreaElement result = querySelector("#getStructResult");
+
+ _calculatorClient
+ .getStruct(int.parse(structKey.value))
+ .then((SharedStruct s) {
+ result.text = "${s.toString()}";
+ });
+ }
+}
diff --git a/tutorial/dart/client/web/index.html b/tutorial/dart/client/web/index.html
new file mode 100644
index 0000000..64b184e
--- /dev/null
+++ b/tutorial/dart/client/web/index.html
@@ -0,0 +1,36 @@
+<!--
+ * 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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Thrift Tutorial</title>
+ <link rel="stylesheet" href="styles.css">
+ <script async src="client.dart" type="application/dart"></script>
+ <script async src="packages/browser/dart.js"></script>
+</head>
+
+<body>
+
+ <div id="output"></div>
+
+</body>
+</html>
diff --git a/tutorial/dart/client/web/styles.css b/tutorial/dart/client/web/styles.css
new file mode 100644
index 0000000..c031502
--- /dev/null
+++ b/tutorial/dart/client/web/styles.css
@@ -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.
+ */
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 10px;
+ font-family: 'Roboto', sans-serif;
+}
+
+h3 {
+ border-bottom: solid;
+ border-width: thin;
+ padding-top: 20px;
+}
diff --git a/tutorial/dart/console_client/bin/main.dart b/tutorial/dart/console_client/bin/main.dart
new file mode 100644
index 0000000..fda206a
--- /dev/null
+++ b/tutorial/dart/console_client/bin/main.dart
@@ -0,0 +1,149 @@
+/// 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:tutorial/tutorial.dart';
+
+TTransport _transport;
+Calculator _calculator;
+int logid = 0;
+
+const Map<String, int> operationLookup = const {
+ '+': Operation.ADD,
+ '-': Operation.SUBTRACT,
+ '*': Operation.MULTIPLY,
+ '/': Operation.DIVIDE
+};
+
+main(List<String> args) {
+ Logger.root.level = Level.ALL;
+ Logger.root.onRecord.listen((LogRecord rec) {
+ print('${rec.level.name}: ${rec.time}: ${rec.message}');
+ });
+
+ var parser = new ArgParser();
+ parser.addOption('port', defaultsTo: '9090', help: 'The port to connect to');
+
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+ } catch (e) {
+ results = null;
+ }
+
+ if (results == null) {
+ print(parser.usage);
+ exit(0);
+ }
+
+ int port = int.parse(results['port']);
+
+ _initConnection(port).then((_) => _run());
+}
+
+Future _initConnection(int port) async {
+ var socket = await Socket.connect('127.0.0.1', port);
+ _transport = new TAsyncClientSocketTransport(
+ new TTcpSocket(socket), new TMessageReader(new TBinaryProtocolFactory()));
+ TProtocol protocol = new TBinaryProtocol(_transport);
+ await _transport.open();
+
+ _calculator = new CalculatorClient(protocol);
+}
+
+Future _run() async {
+ _help();
+
+ while (true) {
+ stdout.write("> ");
+ var input = stdin.readLineSync();
+ var parts = input.split(' ');
+ var command = parts[0];
+ var args = parts.length > 1 ? parts.sublist(1) : [];
+
+ switch (command) {
+ case 'ping':
+ await _ping();
+ break;
+
+ case 'add':
+ await _add(int.parse(args[0]), int.parse(args[1]));
+ break;
+
+ case 'calc':
+ int op = operationLookup[args[1]];
+ if (!Operation.VALID_VALUES.contains(op)) {
+ stdout.writeln('Unknown operator ${args[1]}');
+ break;
+ }
+
+ var work = new Work()
+ ..num1 = int.parse(args[0])
+ ..op = op
+ ..num2 = int.parse(args[2])
+ ..comment = args.length > 3 ? args[3] : '';
+
+ await _calc(work);
+ break;
+
+ case 'struct':
+ await _struct(int.parse(args[0]));
+ break;
+
+ case 'help':
+ default:
+ _help();
+ break;
+ }
+ }
+}
+
+void _help() {
+ stdout.writeln('Commands:');
+ stdout.writeln(' help');
+ stdout.writeln(' ping');
+ stdout.writeln(' add x y');
+ stdout.writeln(' calc x op y [comment]');
+ stdout.writeln(' struct id');
+ stdout.writeln('');
+}
+
+Future _ping() async {
+ await _calculator.ping();
+ stdout.writeln('ping succeeded');
+}
+
+Future _add(int x, int y) async {
+ int result = await _calculator.add(x, y);
+ stdout.writeln('= $result');
+}
+
+Future _calc(Work work) async {
+ int result = await _calculator.calculate(logid++, work);
+ stdout.writeln('= $result');
+}
+
+Future _struct(int key) async {
+ var struct = await _calculator.getStruct(key);
+ stdout.writeln(struct.toString());
+}
diff --git a/tutorial/dart/console_client/pubspec.yaml b/tutorial/dart/console_client/pubspec.yaml
new file mode 100644
index 0000000..3db9fb9
--- /dev/null
+++ b/tutorial/dart/console_client/pubspec.yaml
@@ -0,0 +1,34 @@
+# 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.
+
+name: tutorial_console_client
+version: 1.0.0-dev
+description: >
+ A Dart console client to implementation of the Apache Thrift tutorial
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+ sdk: ^1.12.0
+dependencies:
+ args: ^0.13.0
+ collection: ^1.1.0
+ shared:
+ path: ../gen-dart/shared
+ thrift:
+ path: ../../../lib/dart
+ tutorial:
+ path: ../gen-dart/tutorial
diff --git a/tutorial/dart/server/bin/main.dart b/tutorial/dart/server/bin/main.dart
new file mode 100644
index 0000000..b8ac30d
--- /dev/null
+++ b/tutorial/dart/server/bin/main.dart
@@ -0,0 +1,163 @@
+/// 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:logging/logging.dart';
+import 'package:thrift/thrift.dart';
+import 'package:thrift/thrift_console.dart';
+import 'package:tutorial/tutorial.dart';
+import 'package:shared/shared.dart';
+
+TProtocol _protocol;
+TProcessor _processor;
+WebSocket _webSocket;
+
+main(List<String> args) {
+ Logger.root.level = Level.ALL;
+ Logger.root.onRecord.listen((LogRecord rec) {
+ print('${rec.level.name}: ${rec.time}: ${rec.message}');
+ });
+
+ var parser = new ArgParser();
+ parser.addOption('port', defaultsTo: '9090', help: 'The port to listen on');
+ parser.addOption('type',
+ defaultsTo: 'ws',
+ allowed: ['ws', 'tcp'],
+ help: 'The type of socket',
+ allowedHelp: {'ws': 'WebSocket', 'tcp': 'TCP Socket'});
+
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+ } catch (e) {
+ results = null;
+ }
+
+ if (results == null) {
+ print(parser.usage);
+ exit(0);
+ }
+
+ int port = int.parse(results['port']);
+ String socketType = results['type'];
+
+ if (socketType == 'tcp') {
+ _runTcpServer(port);
+ } else if (socketType == 'ws') {
+ _runWebSocketServer(port);
+ }
+}
+
+Future _runWebSocketServer(int port) async {
+ var httpServer = await HttpServer.bind('127.0.0.1', port);
+ print('listening for WebSocket connections on $port');
+
+ httpServer.listen((HttpRequest request) async {
+ if (request.uri.path == '/ws') {
+ _webSocket = await WebSocketTransformer.upgrade(request);
+ await _initProcessor(new TWebSocket(_webSocket));
+ } else {
+ print('Invalid path: ${request.uri.path}');
+ }
+ });
+}
+
+Future _runTcpServer(int port) async {
+ var serverSocket = await ServerSocket.bind('127.0.0.1', port);
+ print('listening for TCP connections on $port');
+
+ Socket socket = await serverSocket.first;
+ await _initProcessor(new TTcpSocket(socket));
+}
+
+Future _initProcessor(TSocket socket) async {
+ TServerSocketTransport transport = new TServerSocketTransport(socket);
+ transport.onIncomingMessage.listen(_processMessage);
+ _processor = new CalculatorProcessor(new CalculatorServer());
+ _protocol = new TBinaryProtocol(transport);
+ await _protocol.transport.open();
+
+ print('connected');
+}
+
+Future _processMessage(_) async {
+ _processor.process(_protocol, _protocol);
+}
+
+class CalculatorServer implements Calculator {
+ final Map<int, SharedStruct> _log = {};
+
+ Future ping() async {
+ print('ping()');
+ }
+
+ Future<int> add(int num1, int num2) async {
+ print('add($num1, $num2)');
+
+ return num1 + num2;
+ }
+
+ Future<int> calculate(int logid, Work work) async {
+ print('calulate($logid, ${work.toString()})');
+
+ int val;
+
+ switch (work.op) {
+ case Operation.ADD:
+ val = work.num1 + work.num2;
+ break;
+
+ case Operation.SUBTRACT:
+ val = work.num1 - work.num2;
+ break;
+
+ case Operation.MULTIPLY:
+ val = work.num1 * work.num2;
+ break;
+
+ case Operation.DIVIDE:
+ if (work.num2 == 0) {
+ var x = new InvalidOperation();
+ x.whatOp = work.op;
+ x.why = 'Cannot divide by 0';
+ throw x;
+ }
+ val = (work.num1 / work.num2).floor();
+ break;
+ }
+
+ var log = new SharedStruct();
+ log.key = logid;
+ log.value = '$val "${work.comment}"';
+ this._log[logid] = log;
+
+ return val;
+ }
+
+ Future zip() async {
+ print('zip()');
+ }
+
+ Future<SharedStruct> getStruct(int key) async {
+ print('getStruct($key)');
+
+ return _log[key];
+ }
+}
diff --git a/tutorial/dart/server/pubspec.yaml b/tutorial/dart/server/pubspec.yaml
new file mode 100644
index 0000000..f502974
--- /dev/null
+++ b/tutorial/dart/server/pubspec.yaml
@@ -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.
+
+name: tutorial_server
+version: 1.0.0-dev
+description: A Dart server to support the Apache Thrift tutorial
+author: Mark Erickson <mark.erickson@workiva.com>
+homepage: https://github.com/apache/thrift
+environment:
+ sdk: ^1.12.0
+dependencies:
+ args: ^0.13.0
+ shared:
+ path: ../gen-dart/shared
+ thrift:
+ path: ../../../lib/dart
+ tutorial:
+ path: ../gen-dart/tutorial
diff --git a/tutorial/shared.thrift b/tutorial/shared.thrift
index 60e8e7a..386000b 100644
--- a/tutorial/shared.thrift
+++ b/tutorial/shared.thrift
@@ -24,6 +24,7 @@
namespace cpp shared
namespace d share // "shared" would collide with the eponymous D keyword.
+namespace dart shared
namespace java shared
namespace perl shared
namespace php shared
diff --git a/tutorial/tutorial.thrift b/tutorial/tutorial.thrift
index 571ab8e..1149edf 100644
--- a/tutorial/tutorial.thrift
+++ b/tutorial/tutorial.thrift
@@ -64,6 +64,7 @@
*/
namespace cpp tutorial
namespace d tutorial
+namespace dart tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial