THRIFT-2067 C++: all generated objects provide ostream operator<<
diff --git a/compiler/cpp/src/generate/t_cpp_generator.cc b/compiler/cpp/src/generate/t_cpp_generator.cc
index 3a190f5..85eeeef 100755
--- a/compiler/cpp/src/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/generate/t_cpp_generator.cc
@@ -127,6 +127,7 @@
void generate_struct_writer (std::ofstream& out, t_struct* tstruct, bool pointers=false);
void generate_struct_result_writer (std::ofstream& out, t_struct* tstruct, bool pointers=false);
void generate_struct_swap (std::ofstream& out, t_struct* tstruct);
+ void generate_struct_ostream_operator(std::ofstream& out, t_struct* tstruct);
/**
* Service-level generation functions
@@ -230,6 +231,8 @@
const char* suffix,
bool include_values);
+ void generate_struct_ostream_operator_decl(std::ofstream& f, t_struct* tstruct);
+
// These handles checking gen_dense_ and checking for duplicates.
void generate_local_reflection(std::ofstream& out, t_type* ttype, bool is_definition);
void generate_local_reflection_pointer(std::ofstream& out, t_type* ttype);
@@ -376,6 +379,7 @@
// Include base types
f_types_ <<
+ "#include <iosfwd>" << endl << endl <<
"#include <thrift/Thrift.h>" << endl <<
"#include <thrift/TApplicationException.h>" << endl <<
"#include <thrift/protocol/TProtocol.h>" << endl <<
@@ -432,7 +436,10 @@
}
// The swap() code needs <algorithm> for std::swap()
- f_types_impl_ << "#include <algorithm>" << endl << endl;
+ f_types_impl_ << "#include <algorithm>" << endl;
+ // for operator<<
+ f_types_impl_ << "#include <ostream>" << endl << endl;
+ f_types_impl_ << "#include <thrift/TToString.h>" << endl << endl;
// Open namespace
ns_open_ = namespace_open(program_->get_namespace("cpp"));
@@ -817,6 +824,7 @@
generate_struct_swap(f_types_impl_, tstruct);
generate_copy_constructor(f_types_impl_, tstruct);
generate_assignment_operator(f_types_impl_, tstruct);
+ generate_struct_ostream_operator(f_types_impl_, tstruct);
}
void t_cpp_generator::generate_copy_constructor(
@@ -1123,6 +1131,11 @@
}
out << endl;
+ // ostream operator<<
+ out << indent() << "friend ";
+ generate_struct_ostream_operator_decl(out, tstruct);
+ out << ";" << endl;
+
indent_down();
indent(out) <<
"};" << endl <<
@@ -1724,6 +1737,95 @@
out << endl;
}
+void t_cpp_generator::generate_struct_ostream_operator_decl(std::ofstream& out,
+ t_struct* tstruct) {
+ out << "std::ostream& operator<<(std::ostream& out, const "
+ << tstruct->get_name() << "& obj)";
+}
+
+namespace struct_ostream_operator_generator
+{
+void generate_required_field_value(std::ofstream& out, const t_field* field)
+{
+ out << " << to_string(obj." << field->get_name() << ")";
+}
+
+void generate_optional_field_value(std::ofstream& out, const t_field* field)
+{
+ out << "; (obj.__isset." << field->get_name() << " ? (out";
+ generate_required_field_value(out, field);
+ out << ") : (out << \"<null>\"))";
+}
+
+void generate_field_value(std::ofstream& out, const t_field* field)
+{
+ if (field->get_req() == t_field::T_OPTIONAL)
+ generate_optional_field_value(out, field);
+ else
+ generate_required_field_value(out, field);
+}
+
+void generate_field_name(std::ofstream& out, const t_field* field)
+{
+ out << "\"" << field->get_name() << "=\"";
+}
+
+void generate_field(std::ofstream& out, const t_field* field)
+{
+ generate_field_name(out, field);
+ generate_field_value(out, field);
+}
+
+void generate_fields(std::ofstream& out,
+ const vector<t_field*>& fields,
+ const std::string& indent)
+{
+ const vector<t_field*>::const_iterator beg = fields.begin();
+ const vector<t_field*>::const_iterator end = fields.end();
+
+ for (vector<t_field*>::const_iterator it = beg; it != end; ++it) {
+ out << indent << "out << ";
+
+ if (it != beg) {
+ out << "\", \" << ";
+ }
+
+ generate_field(out, *it);
+ out << ";" << endl;
+ }
+}
+
+
+}
+
+/**
+ * Generates operator<<
+ */
+void t_cpp_generator::generate_struct_ostream_operator(std::ofstream& out,
+ t_struct* tstruct) {
+ out << indent();
+ generate_struct_ostream_operator_decl(out, tstruct);
+ out << " {" << endl;
+
+ indent_up();
+
+ out <<
+ indent() << "using apache::thrift::to_string;" << endl <<
+ indent() << "out << \"" << tstruct->get_name() << "(\";" << endl;
+
+ struct_ostream_operator_generator::generate_fields(out,
+ tstruct->get_members(),
+ indent());
+
+ out <<
+ indent() << "out << \")\";" << endl <<
+ indent() << "return out;" << endl;
+
+ indent_down();
+ out << "}" << endl << endl;
+}
+
+
/**
* Generates a thrift service. In C++, this comprises an entirely separate
* header and source file. The header file defines the methods and includes
diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am
index ab9108a..4bd40fb 100755
--- a/lib/cpp/Makefile.am
+++ b/lib/cpp/Makefile.am
@@ -131,7 +131,8 @@
src/thrift/TProcessor.h \
src/thrift/TApplicationException.h \
src/thrift/TLogging.h \
- src/thrift/cxxfunctional.h
+ src/thrift/cxxfunctional.h \
+ src/thrift/TToString.h
include_concurrencydir = $(include_thriftdir)/concurrency
include_concurrency_HEADERS = \
diff --git a/lib/cpp/src/thrift/TToString.h b/lib/cpp/src/thrift/TToString.h
new file mode 100644
index 0000000..c160e09
--- /dev/null
+++ b/lib/cpp/src/thrift/TToString.h
@@ -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.
+ */
+
+#ifndef _THRIFT_TOSTRING_H_
+#define _THRIFT_TOSTRING_H_ 1
+
+#include <boost/lexical_cast.hpp>
+
+#include <vector>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+
+namespace apache { namespace thrift {
+
+template <typename T>
+std::string to_string(const T& t) {
+ return boost::lexical_cast<std::string>(t);
+}
+
+template <typename K, typename V>
+std::string to_string(const std::map<K, V>& m);
+
+template <typename T>
+std::string to_string(const std::set<T>& s);
+
+template <typename T>
+std::string to_string(const std::vector<T>& t);
+
+template <typename K, typename V>
+std::string to_string(const typename std::pair<K, V>& v) {
+ std::ostringstream o;
+ o << to_string(v.first) << ": " << to_string(v.second);
+ return o.str();
+}
+
+template <typename T>
+std::string to_string(const T& beg, const T& end)
+{
+ std::ostringstream o;
+ for (T it = beg; it != end; ++it) {
+ if (it != beg)
+ o << ", ";
+ o << to_string(*it);
+ }
+ return o.str();
+}
+
+template <typename T>
+std::string to_string(const std::vector<T>& t) {
+ std::ostringstream o;
+ o << "[" << to_string(t.begin(), t.end()) << "]";
+ return o.str();
+}
+
+template <typename K, typename V>
+std::string to_string(const std::map<K, V>& m) {
+ std::ostringstream o;
+ o << "{" << to_string(m.begin(), m.end()) << "}";
+ return o.str();
+}
+
+template <typename T>
+std::string to_string(const std::set<T>& s) {
+ std::ostringstream o;
+ o << "{" << to_string(s.begin(), s.end()) << "}";
+ return o.str();
+}
+
+}} // apache::thrift
+
+#endif // _THRIFT_TOSTRING_H_
diff --git a/lib/cpp/test/Makefile.am b/lib/cpp/test/Makefile.am
index 6779ac6..c1fad3e 100755
--- a/lib/cpp/test/Makefile.am
+++ b/lib/cpp/test/Makefile.am
@@ -82,7 +82,8 @@
UnitTestMain.cpp \
TMemoryBufferTest.cpp \
TBufferBaseTest.cpp \
- Base64Test.cpp
+ Base64Test.cpp \
+ ToStringTest.cpp
if !WITH_BOOSTTHREADS
UnitTests_SOURCES += \
diff --git a/lib/cpp/test/ToStringTest.cpp b/lib/cpp/test/ToStringTest.cpp
new file mode 100644
index 0000000..1a89c11
--- /dev/null
+++ b/lib/cpp/test/ToStringTest.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 <vector>
+#include <map>
+
+#include <boost/test/auto_unit_test.hpp>
+
+#include <thrift/TToString.h>
+
+#include "gen-cpp/ThriftTest_types.h"
+#include "gen-cpp/OptionalRequiredTest_types.h"
+#include "gen-cpp/DebugProtoTest_types.h"
+
+using apache::thrift::to_string;
+
+BOOST_AUTO_TEST_SUITE( ToStringTest )
+
+BOOST_AUTO_TEST_CASE( base_types_to_string ) {
+ BOOST_CHECK_EQUAL(to_string(10), "10");
+ BOOST_CHECK_EQUAL(to_string(true), "1");
+ BOOST_CHECK_EQUAL(to_string('a'), "a");
+ BOOST_CHECK_EQUAL(to_string(1.2), "1.2");
+ BOOST_CHECK_EQUAL(to_string("abc"), "abc");
+}
+
+BOOST_AUTO_TEST_CASE( empty_vector_to_string ) {
+ std::vector<int> l;
+ BOOST_CHECK_EQUAL(to_string(l), "[]");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_vector_to_string ) {
+ std::vector<int> l;
+ l.push_back(100);
+ BOOST_CHECK_EQUAL(to_string(l), "[100]");
+}
+
+BOOST_AUTO_TEST_CASE( multiple_item_vector_to_string ) {
+ std::vector<int> l;
+ l.push_back(100);
+ l.push_back(150);
+ BOOST_CHECK_EQUAL(to_string(l), "[100, 150]");
+}
+
+BOOST_AUTO_TEST_CASE( empty_map_to_string ) {
+ std::map<int, std::string> m;
+ BOOST_CHECK_EQUAL(to_string(m), "{}");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_map_to_string ) {
+ std::map<int, std::string> m;
+ m[12] = "abc";
+ BOOST_CHECK_EQUAL(to_string(m), "{12: abc}");
+}
+
+BOOST_AUTO_TEST_CASE( multi_item_map_to_string ) {
+ std::map<int, std::string> m;
+ m[12] = "abc";
+ m[31] = "xyz";
+ BOOST_CHECK_EQUAL(to_string(m), "{12: abc, 31: xyz}");
+}
+
+BOOST_AUTO_TEST_CASE( empty_set_to_string ) {
+ std::set<char> s;
+ BOOST_CHECK_EQUAL(to_string(s), "{}");
+}
+
+BOOST_AUTO_TEST_CASE( single_item_set_to_string ) {
+ std::set<char> s;
+ s.insert('c');
+ BOOST_CHECK_EQUAL(to_string(s), "{c}");
+}
+
+BOOST_AUTO_TEST_CASE( multi_item_set_to_string ) {
+ std::set<char> s;
+ s.insert('a');
+ s.insert('z');
+ BOOST_CHECK_EQUAL(to_string(s), "{a, z}");
+}
+
+BOOST_AUTO_TEST_CASE( generated_empty_object_to_string ) {
+ thrift::test::EmptyStruct e;
+ BOOST_CHECK_EQUAL(to_string(e), "EmptyStruct()");
+}
+
+BOOST_AUTO_TEST_CASE( generated_single_basic_field_object_to_string ) {
+ thrift::test::StructA a;
+ a.__set_s("abcd");
+ BOOST_CHECK_EQUAL(to_string(a), "StructA(s=abcd)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_two_basic_fields_object_to_string ) {
+ thrift::test::Bonk a;
+ a.__set_message("abcd");
+ a.__set_type(1234);
+ BOOST_CHECK_EQUAL(to_string(a), "Bonk(message=abcd, type=1234)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_optional_fields_object_to_string ) {
+ thrift::test::Tricky2 a;
+ BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=<null>)");
+ a.__set_im_optional(123);
+ BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=123)");
+}
+
+BOOST_AUTO_TEST_CASE( generated_nested_object_to_string ) {
+ thrift::test::OneField a;
+ BOOST_CHECK_EQUAL(to_string(a), "OneField(field=EmptyStruct())");
+}
+
+BOOST_AUTO_TEST_CASE( generated_nested_list_object_to_string ) {
+ thrift::test::ListBonks l;
+ l.bonk.assign(2, thrift::test::Bonk());
+ l.bonk[0].__set_message("a");
+ l.bonk[1].__set_message("b");
+
+ BOOST_CHECK_EQUAL(to_string(l),
+ "ListBonks(bonk=[Bonk(message=a, type=0), Bonk(message=b, type=0)])");
+}
+
+BOOST_AUTO_TEST_SUITE_END()