From a4380dcaee83504749ea7e1b9f7e00ce95149d01 Mon Sep 17 00:00:00 2001 From: Konrad Grochowski Date: Tue, 2 Sep 2014 16:00:47 +0200 Subject: [PATCH] THRIFT-2067 C++: all generated objects provide ostream operator<< --- compiler/cpp/src/generate/t_cpp_generator.cc | 104 +++++++++++++- lib/cpp/Makefile.am | 3 +- lib/cpp/src/thrift/TToString.h | 89 ++++++++++++ lib/cpp/test/Makefile.am | 3 +- lib/cpp/test/ToStringTest.cpp | 137 +++++++++++++++++++ 5 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 lib/cpp/src/thrift/TToString.h create mode 100644 lib/cpp/test/ToStringTest.cpp diff --git a/compiler/cpp/src/generate/t_cpp_generator.cc b/compiler/cpp/src/generate/t_cpp_generator.cc index 3a190f5b16a..85eeeef5b65 100755 --- a/compiler/cpp/src/generate/t_cpp_generator.cc +++ b/compiler/cpp/src/generate/t_cpp_generator.cc @@ -127,6 +127,7 @@ class t_cpp_generator : public t_oop_generator { 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 @@ class t_cpp_generator : public t_oop_generator { 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 @@ void t_cpp_generator::init_generator() { // Include base types f_types_ << + "#include " << endl << endl << "#include " << endl << "#include " << endl << "#include " << endl << @@ -432,7 +436,10 @@ void t_cpp_generator::init_generator() { } // The swap() code needs for std::swap() - f_types_impl_ << "#include " << endl << endl; + f_types_impl_ << "#include " << endl; + // for operator<< + f_types_impl_ << "#include " << endl << endl; + f_types_impl_ << "#include " << endl << endl; // Open namespace ns_open_ = namespace_open(program_->get_namespace("cpp")); @@ -817,6 +824,7 @@ void t_cpp_generator::generate_cpp_struct(t_struct* tstruct, bool is_exception) 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 @@ void t_cpp_generator::generate_struct_declaration(ofstream& out, } 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 @@ void t_cpp_generator::generate_struct_swap(ofstream& out, t_struct* tstruct) { 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 << \"\"))"; +} + +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& fields, + const std::string& indent) +{ + const vector::const_iterator beg = fields.begin(); + const vector::const_iterator end = fields.end(); + + for (vector::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 ab9108a4858..4bd40fbe22e 100755 --- a/lib/cpp/Makefile.am +++ b/lib/cpp/Makefile.am @@ -131,7 +131,8 @@ include_thrift_HEADERS = \ 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 00000000000..c160e09df3f --- /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 + +#include +#include +#include +#include +#include + +namespace apache { namespace thrift { + +template +std::string to_string(const T& t) { + return boost::lexical_cast(t); +} + +template +std::string to_string(const std::map& m); + +template +std::string to_string(const std::set& s); + +template +std::string to_string(const std::vector& t); + +template +std::string to_string(const typename std::pair& v) { + std::ostringstream o; + o << to_string(v.first) << ": " << to_string(v.second); + return o.str(); +} + +template +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 +std::string to_string(const std::vector& t) { + std::ostringstream o; + o << "[" << to_string(t.begin(), t.end()) << "]"; + return o.str(); +} + +template +std::string to_string(const std::map& m) { + std::ostringstream o; + o << "{" << to_string(m.begin(), m.end()) << "}"; + return o.str(); +} + +template +std::string to_string(const std::set& 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 6779ac6558a..c1fad3e3e51 100755 --- a/lib/cpp/test/Makefile.am +++ b/lib/cpp/test/Makefile.am @@ -82,7 +82,8 @@ UnitTests_SOURCES = \ 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 00000000000..1a89c11e94f --- /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 +#include + +#include + +#include + +#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 l; + BOOST_CHECK_EQUAL(to_string(l), "[]"); +} + +BOOST_AUTO_TEST_CASE( single_item_vector_to_string ) { + std::vector l; + l.push_back(100); + BOOST_CHECK_EQUAL(to_string(l), "[100]"); +} + +BOOST_AUTO_TEST_CASE( multiple_item_vector_to_string ) { + std::vector 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 m; + BOOST_CHECK_EQUAL(to_string(m), "{}"); +} + +BOOST_AUTO_TEST_CASE( single_item_map_to_string ) { + std::map m; + m[12] = "abc"; + BOOST_CHECK_EQUAL(to_string(m), "{12: abc}"); +} + +BOOST_AUTO_TEST_CASE( multi_item_map_to_string ) { + std::map 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 s; + BOOST_CHECK_EQUAL(to_string(s), "{}"); +} + +BOOST_AUTO_TEST_CASE( single_item_set_to_string ) { + std::set s; + s.insert('c'); + BOOST_CHECK_EQUAL(to_string(s), "{c}"); +} + +BOOST_AUTO_TEST_CASE( multi_item_set_to_string ) { + std::set 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=)"); + 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()