PROTON-1288: c++ provide convenient access to message/filter maps proton::map type to wrap map values for message properties/annotations and source filters.
Has the following features: - type safe template for the standard AMQP 'restricted map' types - simple get()/put() interface for simple check/set individual properties - converts directly to/from a proton::value efficient pass-through, no re-coding - converts directly to/from std:map, std::unordered_map etc. - encode/decode only as required Intended use: use proton::map directly for simple get()/put(), convert to a standard map or sequence type for more complex use (iteration, preserving encoded order etc.) Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/864519dd Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/864519dd Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/864519dd Branch: refs/heads/PROTON-1488 Commit: 864519ddca6c45a10ec01598b3ff3b33e5591561 Parents: f2df847 Author: Alan Conway <[email protected]> Authored: Wed May 24 13:03:11 2017 -0400 Committer: Alan Conway <[email protected]> Committed: Wed May 24 15:37:11 2017 -0400 ---------------------------------------------------------------------- examples/cpp/CMakeLists.txt | 1 + examples/cpp/README.dox | 6 + examples/cpp/example_test.py | 41 ++- examples/cpp/message_properties.cpp | 22 +- examples/exampletest.py | 5 +- proton-c/bindings/cpp/CMakeLists.txt | 3 +- .../cpp/include/proton/codec/encoder.hpp | 13 + .../cpp/include/proton/internal/cached_map.hpp | 91 ------- .../include/proton/internal/pn_unique_ptr.hpp | 6 + .../cpp/include/proton/internal/type_traits.hpp | 1 - proton-c/bindings/cpp/include/proton/map.hpp | 128 ++++++++++ .../bindings/cpp/include/proton/message.hpp | 28 +- proton-c/bindings/cpp/include/proton/source.hpp | 8 +- proton-c/bindings/cpp/include/proton/value.hpp | 3 +- proton-c/bindings/cpp/src/cached_map.cpp | 130 ---------- .../bindings/cpp/src/connection_driver_test.cpp | 38 +++ proton-c/bindings/cpp/src/include/test_bits.hpp | 1 + proton-c/bindings/cpp/src/map.cpp | 254 +++++++++++++++++++ proton-c/bindings/cpp/src/map_test.cpp | 126 +++++++++ proton-c/bindings/cpp/src/message.cpp | 92 +++---- proton-c/bindings/cpp/src/message_test.cpp | 19 +- proton-c/bindings/cpp/src/node_options.cpp | 3 +- proton-c/bindings/cpp/src/scalar_base.cpp | 3 +- proton-c/bindings/cpp/src/source.cpp | 26 +- proton-c/bindings/cpp/src/value.cpp | 3 + 25 files changed, 697 insertions(+), 354 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/examples/cpp/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 236aac7..ecd88cf 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -41,6 +41,7 @@ foreach(example helloworld_direct simple_recv simple_send + message_properties scheduled_send_03 direct_recv direct_send http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/examples/cpp/README.dox ---------------------------------------------------------------------- diff --git a/examples/cpp/README.dox b/examples/cpp/README.dox index 447d3ad..327dbef 100644 --- a/examples/cpp/README.dox +++ b/examples/cpp/README.dox @@ -50,6 +50,12 @@ on 127.0.0.1:5672. Simply prints out the body of received messages. */ +/** @example message_properties.cpp + +Shows how to set and examine message properties. + +*/ + /** @example direct_send.cpp Accepts an incoming connection and then sends like `simple_send`. You can http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/examples/cpp/example_test.py ---------------------------------------------------------------------- diff --git a/examples/cpp/example_test.py b/examples/cpp/example_test.py index f9a0825..5773142 100644 --- a/examples/cpp/example_test.py +++ b/examples/cpp/example_test.py @@ -125,53 +125,53 @@ class ContainerExampleTest(BrokerTestCase): broker_exe = "broker" def test_helloworld(self): - self.assertEqual('Hello World!\n', self.proc(["helloworld", self.addr]).wait_exit()) + self.assertMultiLineEqual('Hello World!\n', self.proc(["helloworld", self.addr]).wait_exit()) def test_helloworld_direct(self): with TestPort() as tp: - self.assertEqual('Hello World!\n', self.proc(["helloworld_direct", tp.addr]).wait_exit()) + self.assertMultiLineEqual('Hello World!\n', self.proc(["helloworld_direct", tp.addr]).wait_exit()) def test_simple_send_recv(self): - self.assertEqual("all messages confirmed\n", + self.assertMultiLineEqual("all messages confirmed\n", self.proc(["simple_send", "-a", self.addr]).wait_exit()) - self.assertEqual(recv_expect("simple_recv", self.addr), self.proc(["simple_recv", "-a", self.addr]).wait_exit()) + self.assertMultiLineEqual(recv_expect("simple_recv", self.addr), self.proc(["simple_recv", "-a", self.addr]).wait_exit()) def test_simple_recv_send(self): # Start receiver first, then run sender""" recv = self.proc(["simple_recv", "-a", self.addr]) - self.assertEqual("all messages confirmed\n", + self.assertMultiLineEqual("all messages confirmed\n", self.proc(["simple_send", "-a", self.addr]).wait_exit()) - self.assertEqual(recv_expect("simple_recv", self.addr), recv.wait_exit()) + self.assertMultiLineEqual(recv_expect("simple_recv", self.addr), recv.wait_exit()) def test_simple_send_direct_recv(self): with TestPort() as tp: addr = "%s/examples" % tp.addr recv = self.proc(["direct_recv", "-a", addr], "listening") - self.assertEqual("all messages confirmed\n", + self.assertMultiLineEqual("all messages confirmed\n", self.proc(["simple_send", "-a", addr]).wait_exit()) - self.assertEqual(recv_expect("direct_recv", addr), recv.wait_exit()) + self.assertMultiLineEqual(recv_expect("direct_recv", addr), recv.wait_exit()) def test_simple_recv_direct_send(self): with TestPort() as tp: addr = "%s/examples" % tp.addr send = self.proc(["direct_send", "-a", addr], "listening") - self.assertEqual(recv_expect("simple_recv", addr), + self.assertMultiLineEqual(recv_expect("simple_recv", addr), self.proc(["simple_recv", "-a", addr]).wait_exit()) - self.assertEqual( + self.assertMultiLineEqual( "direct_send listening on %s\nall messages confirmed\n" % addr, send.wait_exit()) def test_request_response(self): server = self.proc(["server", "-a", self.addr], "connected") - self.assertEqual(CLIENT_EXPECT, + self.assertMultiLineEqual(CLIENT_EXPECT, self.proc(["client", "-a", self.addr]).wait_exit()) def test_request_response_direct(self): with TestPort() as tp: addr = "%s/examples" % tp.addr server = self.proc(["server_direct", "-a", addr], "listening") - self.assertEqual(CLIENT_EXPECT, + self.assertMultiLineEqual(CLIENT_EXPECT, self.proc(["client", "-a", addr]).wait_exit()) def test_flow_control(self): @@ -181,7 +181,7 @@ success: Example 3: drain without credit success: Exmaple 4: high/low watermark """ with TestPort() as tp: - self.assertEqual(want, self.proc(["flow_control", "--address", tp.addr, "--quiet"]).wait_exit()) + self.assertMultiLineEqual(want, self.proc(["flow_control", "--address", tp.addr, "--quiet"]).wait_exit()) def test_encode_decode(self): want=""" @@ -208,7 +208,7 @@ list[int(42), boolean(0), symbol(x)] map{string(k1):int(42), symbol(k2):boolean(0)} """ self.maxDiff = None - self.assertEqual(want, self.proc(["encode_decode"]).wait_exit()) + self.assertMultiLineEqual(want, self.proc(["encode_decode"]).wait_exit()) def test_scheduled_send_03(self): # Output should be a bunch of "send" lines but can't guarantee exactly how many. @@ -224,6 +224,19 @@ map{string(k1):int(42), symbol(k2):boolean(0)} except ProcError: # File not found, not a C++11 build. pass + def test_message_properties(self): + expect="""using put/get: short=123 string=foo symbol=sym +using coerce: short(as long)=123 +props[short]=123 +props[string]=foo +props[symbol]=sym +short=42 string=bar +expected conversion_error: "unexpected type, want: uint got: int" +expected conversion_error: "unexpected type, want: uint got: string" +""" + self.assertMultiLineEqual(expect, self.proc(["message_properties"]).wait_exit()) + + class ContainerExampleSSLTest(BrokerTestCase): """Run the SSL container examples, verify they behave as expected.""" http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/examples/cpp/message_properties.cpp ---------------------------------------------------------------------- diff --git a/examples/cpp/message_properties.cpp b/examples/cpp/message_properties.cpp index 64d597b..cb5c6b8 100644 --- a/examples/cpp/message_properties.cpp +++ b/examples/cpp/message_properties.cpp @@ -31,7 +31,6 @@ int main(int argc, char **argv) { m.properties().put("short", int16_t(123)); m.properties().put("string", "foo"); m.properties().put("symbol", proton::symbol("sym")); - m.properties().put("bool", true); // Examining properties using proton::get() @@ -53,25 +52,26 @@ int main(int argc, char **argv) { << " short=" << i << " string=" << s << " symbol=" << m.properties().get("symbol") - << " bool=" << m.properties().get("bool") << std::endl; - // Converting properties to a compatible type + // Converting properties to a convertible type std::cout << "using coerce:" - << " short(as int)=" << proton::coerce<int>(m.properties().get("short")) - << " bool(as int)=" << proton::coerce<int>(m.properties().get("bool")) + << " short(as long)=" + << proton::coerce<long>(m.properties().get("short")) << std::endl; - // Extract the properties map for more complex map operations. - proton::property_std_map props; - proton::get(m.properties().value(), props); - for (proton::property_std_map::iterator i = props.begin(); i != props.end(); ++i) { + // Extract the properties as a std::map for more complex map operations. + // You can use other map and sequence types to hold a map, see @ref types_page + typedef std::map<std::string, proton::scalar> property_map; + property_map props; + proton::get(m.properties(), props); + for (property_map::iterator i = props.begin(); i != props.end(); ++i) { std::cout << "props[" << i->first << "]=" << i->second << std::endl; } props["string"] = "bar"; props["short"] = 42; - // Update the properties in the message from props - m.properties().value() = props; + // Update the properties in the message from the modified props map + m.properties() = props; std::cout << "short=" << m.properties().get("short") << " string=" << m.properties().get("string") http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/examples/exampletest.py ---------------------------------------------------------------------- diff --git a/examples/exampletest.py b/examples/exampletest.py index bb055a1..5c53616 100644 --- a/examples/exampletest.py +++ b/examples/exampletest.py @@ -166,10 +166,13 @@ class TestCase(unittest.TestCase): super(TestCase, self).tearDown() if _tc_missing('assertIn'): - def assertIn(self, a, b): self.assertTrue(a in b, "%r not in %r" % (a, b)) + if _tc_missing('assertMultiLineEqual'): + def assertMultiLineEqual(self, a, b): + self.assertEqual(a, b) + class ExampleTestCase(TestCase): """TestCase that manages started processes""" def setUp(self): http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/CMakeLists.txt b/proton-c/bindings/cpp/CMakeLists.txt index 04cb568..d1c6fd1 100644 --- a/proton-c/bindings/cpp/CMakeLists.txt +++ b/proton-c/bindings/cpp/CMakeLists.txt @@ -29,7 +29,7 @@ add_definitions(${CXX_WARNING_FLAGS}) set(qpid-proton-cpp-source src/binary.cpp src/byte_array.cpp - src/cached_map.cpp + src/map.cpp src/connection.cpp src/connection_options.cpp src/connector.cpp @@ -176,6 +176,7 @@ add_cpp_test(connection_driver_test) add_cpp_test(thread_safe_test) add_cpp_test(interop_test ${CMAKE_SOURCE_DIR}/tests) add_cpp_test(message_test) +add_cpp_test(map_test) add_cpp_test(scalar_test) add_cpp_test(value_test) add_cpp_test(container_test) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/codec/encoder.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/codec/encoder.hpp b/proton-c/bindings/cpp/include/proton/codec/encoder.hpp index 222e95b..2610021 100644 --- a/proton-c/bindings/cpp/include/proton/codec/encoder.hpp +++ b/proton-c/bindings/cpp/include/proton/codec/encoder.hpp @@ -194,6 +194,19 @@ template <> struct is_encodable<value> : public true_type {}; using is_encodable_impl::is_encodable; +// Metafunction to test if a class looks like an encodable map from K to T. +template <class M, class K, class T, class Enable = void> +struct is_encodable_map : public internal::false_type {}; + +template <class M, class K, class T> struct is_encodable_map< + M, K, T, typename internal::enable_if< + internal::is_same<K, typename M::key_type>::value && + internal::is_same<T, typename M::mapped_type>::value && + is_encodable<M>::value + >::type + > : public internal::true_type {}; + + /// @endcond } // codec http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/internal/cached_map.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/internal/cached_map.hpp b/proton-c/bindings/cpp/include/proton/internal/cached_map.hpp deleted file mode 100644 index 4f388a1..0000000 --- a/proton-c/bindings/cpp/include/proton/internal/cached_map.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef PROTON_CPP_CACHED_MAP_H -#define PROTON_CPP_CACHED_MAP_H - -/* - * - * 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 "./config.hpp" -#include "./export.hpp" -#include "./pn_unique_ptr.hpp" - -#include <cstddef> - -namespace proton { - -namespace codec { -class decoder; -class encoder; -} - -namespace internal { - -template <class key_type, class value_type> -class map_type_impl; - -/// A convenience class to view and manage AMQP map data contained in -/// a proton::value. An internal cache of the map data is created as -/// needed. -template <class K, class V> -class cached_map; - -template <class K, class V> -PN_CPP_EXTERN proton::codec::decoder& operator>>(proton::codec::decoder& d, cached_map<K,V>& m); -template <class K, class V> -PN_CPP_EXTERN proton::codec::encoder& operator<<(proton::codec::encoder& e, const cached_map<K,V>& m); - -template <class key_type, class value_type> -class PN_CPP_CLASS_EXTERN cached_map { - typedef map_type_impl<key_type, value_type> map_type; - - public: - PN_CPP_EXTERN cached_map(); - PN_CPP_EXTERN cached_map(const cached_map& cm); - PN_CPP_EXTERN cached_map& operator=(const cached_map& cm); -#if PN_CPP_HAS_RVALUE_REFERENCES - PN_CPP_EXTERN cached_map(cached_map&&); - PN_CPP_EXTERN cached_map& operator=(cached_map&&); -#endif - PN_CPP_EXTERN ~cached_map(); - - PN_CPP_EXTERN value_type get(const key_type& k) const; - PN_CPP_EXTERN void put(const key_type& k, const value_type& v); - PN_CPP_EXTERN size_t erase(const key_type& k); - PN_CPP_EXTERN bool exists(const key_type& k) const; - PN_CPP_EXTERN size_t size(); - PN_CPP_EXTERN void clear(); - PN_CPP_EXTERN bool empty(); - - /// @cond INTERNAL - private: - pn_unique_ptr<map_type> map_; - - void make_cached_map(); - - friend PN_CPP_EXTERN proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cached_map<key_type, value_type>& m); - friend PN_CPP_EXTERN proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cached_map<key_type, value_type>& m); - /// @endcond -}; - - -} -} - -#endif // PROTON_CPP_CACHED_MAP_H http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/internal/pn_unique_ptr.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/internal/pn_unique_ptr.hpp b/proton-c/bindings/cpp/include/proton/internal/pn_unique_ptr.hpp index 323c701..a3416e5 100644 --- a/proton-c/bindings/cpp/include/proton/internal/pn_unique_ptr.hpp +++ b/proton-c/bindings/cpp/include/proton/internal/pn_unique_ptr.hpp @@ -29,6 +29,9 @@ namespace proton { namespace internal { +template <class T> class pn_unique_ptr; +template <class T> void swap(pn_unique_ptr<T>& x, pn_unique_ptr<T>& y); + /// A simple unique ownership pointer, used as a return value from /// functions that transfer ownership to the caller. /// @@ -55,6 +58,7 @@ template <class T> class pn_unique_ptr { explicit operator bool() const { return get(); } #endif bool operator !() const { return !get(); } + void swap(pn_unique_ptr& x) { std::swap(ptr_, x.ptr_); } #if PN_CPP_HAS_UNIQUE_PTR operator std::unique_ptr<T>() { T *p = ptr_; ptr_ = 0; return std::unique_ptr<T>(p); } @@ -64,6 +68,8 @@ template <class T> class pn_unique_ptr { T* ptr_; }; +template <class T> void swap(pn_unique_ptr<T>& x, pn_unique_ptr<T>& y) { x.swap(y); } + } // internal } // proton http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/internal/type_traits.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/internal/type_traits.hpp b/proton-c/bindings/cpp/include/proton/internal/type_traits.hpp index 8abba55..bd16d29 100644 --- a/proton-c/bindings/cpp/include/proton/internal/type_traits.hpp +++ b/proton-c/bindings/cpp/include/proton/internal/type_traits.hpp @@ -151,7 +151,6 @@ template <class T> struct is_unknown_integer { template<class T, class = typename enable_if<is_unknown_integer<T>::value>::type> struct known_integer : public integer_type<sizeof(T), is_signed<T>::value> {}; - // Helper base for SFINAE templates. struct sfinae { typedef char yes; http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/map.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/map.hpp b/proton-c/bindings/cpp/include/proton/map.hpp new file mode 100644 index 0000000..8399b16 --- /dev/null +++ b/proton-c/bindings/cpp/include/proton/map.hpp @@ -0,0 +1,128 @@ +#ifndef PROTON_CPP_MAP_H +#define PROTON_CPP_MAP_H + +/* + * + * 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 "./value.hpp" + +#include "internal/pn_unique_ptr.hpp" + +#include <cstddef> + +namespace proton { + +namespace codec { +class decoder; +class encoder; +} + +template <class K, class T> +class map_type_impl; + +template <class K, class T> +class map; + +template <class K, class T> +PN_CPP_EXTERN proton::codec::decoder& operator>>(proton::codec::decoder& d, map<K,T>& m); +template <class K, class T> +PN_CPP_EXTERN proton::codec::encoder& operator<<(proton::codec::encoder& e, const map<K,T>& m); +template <class K, class T> +PN_CPP_EXTERN void swap(map<K,T>&, map<K,T>&); + +/// Used to access standard AMQP property, annotation and filter maps attached +/// to proton::message and proton::source. +/// +/// Provides only basic get()/set() operations for convenience. For more +/// complicated use (iteration, preserving order etc.) you should convert to a +/// standard C++ map type such as std::map. See @ref message_propreties.cpp +/// and @ref types_page. +/// +template <class K, class T> +class PN_CPP_CLASS_EXTERN map { + template <class M, class U=void> + struct assignable_map : + public internal::enable_if<codec::is_encodable_map<M,K,T>::value, U> {}; + + public: + /// Create an empty map value + PN_CPP_EXTERN map(); + + PN_CPP_EXTERN map(const map& cm); + PN_CPP_EXTERN map& operator=(const map& cm); +#if PN_CPP_HAS_RVALUE_REFERENCES + PN_CPP_EXTERN map(map&&); + PN_CPP_EXTERN map& operator=(map&&); +#endif + + PN_CPP_EXTERN ~map(); + + /// Type-safe assign from a compatible map type, e.g. std::map<K,T> - see @types_page + template <class M> + typename assignable_map<M, map&>::type operator=(const M& x) { value(x); return *this;} + + /// Copy from a proton::value. + /// @throw proton::conversion_error if x does not contain a compatible map. + PN_CPP_EXTERN void value(const value& x); + /// Access as a proton::value + PN_CPP_EXTERN proton::value& value(); + /// Access as a proton::value + PN_CPP_EXTERN const proton::value& value() const; + + /// Get the map entry for key k, return T() if no such entry + PN_CPP_EXTERN T get(const K& k) const; + /// Put a map entry for key k. + PN_CPP_EXTERN void put(const K& k, const T& v); + /// Erase the map entry at k + PN_CPP_EXTERN size_t erase(const K& k); + /// True if there is a map entry for k + PN_CPP_EXTERN bool exists(const K& k) const; + /// Number of map entries + PN_CPP_EXTERN size_t size() const; + /// Clear the map value + PN_CPP_EXTERN void clear(); + /// True if the map is empty + PN_CPP_EXTERN bool empty() const; + + ///@cond INTERNAL + explicit map(pn_data_t*); + void reset(pn_data_t*); + ///@endcond + + private: + typedef map_type_impl<K,T> map_type; + mutable internal::pn_unique_ptr<map_type> map_; + mutable proton::value value_; + + void ensure() const; + const map_type& cache() const; + map_type& cache_update(); + proton::value& flush() const; + + friend PN_CPP_EXTERN proton::codec::decoder& operator>> <>(proton::codec::decoder&, map&); + friend PN_CPP_EXTERN proton::codec::encoder& operator<< <>(proton::codec::encoder&, const map&); + friend PN_CPP_EXTERN void swap<>(map&, map&); + /// @endcond +}; + +} // namespace proton + +#endif // PROTON_CPP_MAP_H http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/message.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/message.hpp b/proton-c/bindings/cpp/include/proton/message.hpp index faf2f29..a25f7db 100644 --- a/proton-c/bindings/cpp/include/proton/message.hpp +++ b/proton-c/bindings/cpp/include/proton/message.hpp @@ -27,8 +27,8 @@ #include "./duration.hpp" #include "./timestamp.hpp" #include "./value.hpp" +#include "./map.hpp" -#include "./internal/cached_map.hpp" #include "./internal/pn_unique_ptr.hpp" #include <proton/type_compat.h> @@ -48,11 +48,11 @@ class message { public: /// **Experimental** - A map of string keys and AMQP scalar /// values. - class property_map : public internal::cached_map<std::string, scalar> {}; + typedef map<std::string, scalar> property_map; /// **Experimental** - A map of AMQP annotation keys and AMQP /// values. - class annotation_map : public internal::cached_map<annotation_key, value> {}; + typedef map<annotation_key, value> annotation_map; /// Create an empty message. PN_CPP_EXTERN message(); @@ -243,7 +243,7 @@ class message { /// acquired, by other recipients. // XXX The triple-not in the last sentence above is confusing. - + PN_CPP_EXTERN bool first_acquirer() const; /// Set the first acquirer flag. @@ -287,33 +287,25 @@ class message { /// @} - /// @name Extended attributes + /// @name **Experimental** - Application properties /// @{ /// **Experimental** - Get the application properties map. It can /// be modified in place. PN_CPP_EXTERN property_map& properties(); - /// **Experimental** - Get the application properties map. It can - /// be modified in place. + /// **Experimental** - examine the application properties map. PN_CPP_EXTERN const property_map& properties() const; - /// **Experimental** - Get the message annotations map. It can be - /// modified in place. + /// @name **Experimental** - Annotations + /// + /// Normally used by messaging infrastructure, not applications. + /// @{ PN_CPP_EXTERN annotation_map& message_annotations(); - - /// **Experimental** - Get the message annotations map. It can be - /// modified in place. PN_CPP_EXTERN const annotation_map& message_annotations() const; - /// **Experimental** - Get the delivery annotations map. It can - /// be modified in place. PN_CPP_EXTERN annotation_map& delivery_annotations(); - - /// **Experimental** - Get the delivery annotations map. It can - /// be modified in place. PN_CPP_EXTERN const annotation_map& delivery_annotations() const; - /// @} /// Default priority assigned to new messages. http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/source.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/source.hpp b/proton-c/bindings/cpp/include/proton/source.hpp index 321af9a..59963dd 100644 --- a/proton-c/bindings/cpp/include/proton/source.hpp +++ b/proton-c/bindings/cpp/include/proton/source.hpp @@ -24,7 +24,7 @@ #include "./fwd.hpp" #include "./internal/export.hpp" -#include "./internal/cached_map.hpp" +#include "./map.hpp" #include "./symbol.hpp" #include "./terminus.hpp" #include "./value.hpp" @@ -42,7 +42,7 @@ class source : public terminus { public: /// **Experimental** - A map of AMQP symbol keys and filter /// specifiers. - class filter_map : public internal::cached_map<symbol, value> {}; + typedef map<symbol, value> filter_map; /// Create an empty source. source() : terminus() {} @@ -69,13 +69,15 @@ class source : public terminus { PN_CPP_EXTERN enum distribution_mode distribution_mode() const; /// **Experimental** - Obtain the set of message filters. - PN_CPP_EXTERN filter_map filters() const; + PN_CPP_EXTERN const filter_map& filters() const; private: source(pn_terminus_t* t); source(const sender&); source(const receiver&); + filter_map filters_; + /// @cond INTERNAL friend class proton::internal::factory<source>; friend class sender; http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/include/proton/value.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/value.hpp b/proton-c/bindings/cpp/include/proton/value.hpp index 75927f7..bc7ed86 100644 --- a/proton-c/bindings/cpp/include/proton/value.hpp +++ b/proton-c/bindings/cpp/include/proton/value.hpp @@ -72,7 +72,7 @@ class value : public internal::value_base, private internal::comparable<value> { #endif /// @} - /// Construct from any allowed type T. + /// Copy from any allowed type T. template <class T> value(const T& x, typename assignable<T>::type* = 0) { *this = x; } /// Assign from any allowed type T. @@ -88,7 +88,6 @@ class value : public internal::value_base, private internal::comparable<value> { /// True if the value is null PN_CPP_EXTERN bool empty() const; - /// Reset the value to null/empty PN_CPP_EXTERN void clear(); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/cached_map.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/cached_map.cpp b/proton-c/bindings/cpp/src/cached_map.cpp deleted file mode 100644 index 5411aa1..0000000 --- a/proton-c/bindings/cpp/src/cached_map.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * - * 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 "proton/internal/cached_map.hpp" - -#include "proton/annotation_key.hpp" -#include "proton/scalar.hpp" -#include "proton/value.hpp" -#include "proton/codec/decoder.hpp" -#include "proton/codec/encoder.hpp" -#include "proton/codec/map.hpp" - -#include <map> -#include <string> - -namespace proton { -namespace internal { - - // use std::map as the actual cached_map implementation type -template <class K, class V> -class map_type_impl : public std::map<K, V> {}; - -template <class K, class V> -cached_map<K,V>::cached_map() {} -template <class K, class V> -cached_map<K,V>::cached_map(const cached_map& cm) { if ( !cm.map_ ) return; map_.reset(new map_type(*cm.map_)); } -template <class K, class V> -cached_map<K,V>& cached_map<K,V>::operator=(const cached_map& cm) { - if (&cm != this) { - cached_map<K,V> t; - map_type *m = !cm.map_ ? 0 : new map_type(*cm.map_); - t.map_.reset(map_.release()); - map_.reset(m); - } - return *this; -} - -template <class K, class V> -#if PN_CPP_HAS_RVALUE_REFERENCES -cached_map<K,V>::cached_map(cached_map&& cm) : map_(std::move(cm.map_)) {} -template <class K, class V> -cached_map<K,V>& cached_map<K,V>::operator=(cached_map&& cm) { map_.reset(cm.map_.release()); return *this; } -template <class K, class V> -#endif -cached_map<K,V>::~cached_map() {} - -template <class K, class V> -V cached_map<K,V>::get(const K& k) const { - if ( !map_ ) return V(); - typename map_type::const_iterator i = map_->find(k); - if ( i==map_->end() ) return V(); - return i->second; -} -template <class K, class V> -void cached_map<K,V>::put(const K& k, const V& v) { - if ( !map_ ) make_cached_map(); - (*map_)[k] = v; -} -template <class K, class V> -size_t cached_map<K,V>::erase(const K& k) { - if ( !map_ ) return 0; - return map_->erase(k); -} -template <class K, class V> -bool cached_map<K,V>::exists(const K& k) const { - if ( !map_ ) return false; - return map_->count(k) > 0; -} - -template <class K, class V> -size_t cached_map<K,V>::size() { - if ( !map_ ) return 0; - return map_->size(); -} -template <class K, class V> -void cached_map<K,V>::clear() { - map_.reset(); -} -template <class K, class V> -bool cached_map<K,V>::empty() { - if ( !map_ ) return true; - return map_->empty(); -} - -template <class K, class V> -void cached_map<K,V>::make_cached_map() { map_.reset(new map_type); } - -template <class K, class V> -PN_CPP_EXTERN proton::codec::decoder& operator>>(proton::codec::decoder& d, cached_map<K,V>& m) { - if ( !m.map_ ) m.make_cached_map(); - return d >> *(m.map_); -} -template <class K, class V> -PN_CPP_EXTERN proton::codec::encoder& operator<<(proton::codec::encoder& e, const cached_map<K,V>& m) { - if ( !m.map_ ) return e; - return e << *(m.map_); -} - -// Force the necessary template instantiations so that the library exports the correct symbols -template class PN_CPP_CLASS_EXTERN cached_map<std::string, scalar>; -template class PN_CPP_CLASS_EXTERN cached_map<annotation_key, value>; -template class PN_CPP_CLASS_EXTERN cached_map<symbol, value>; - -template proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cached_map<std::string, scalar>& m); -template proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cached_map<std::string, scalar>& m); -template proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cached_map<annotation_key, value>& m); -template proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cached_map<annotation_key, value>& m); -template proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cached_map<symbol, value>& m); -template proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cached_map<symbol, value>& m); - -} -} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/connection_driver_test.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/connection_driver_test.cpp b/proton-c/bindings/cpp/src/connection_driver_test.cpp index 372240b..760ac55 100644 --- a/proton-c/bindings/cpp/src/connection_driver_test.cpp +++ b/proton-c/bindings/cpp/src/connection_driver_test.cpp @@ -21,6 +21,7 @@ #include "test_bits.hpp" #include "proton_bits.hpp" +#include "proton/container.hpp" #include "proton/io/connection_driver.hpp" #include "proton/io/link_namer.hpp" #include "proton/link.hpp" @@ -248,10 +249,47 @@ void test_no_container() { } catch (proton::error) {} } +void test_link_filters() { + // Propagation of link properties + record_handler ha, hb; + driver_pair e(ha, hb); + + source_options opts; + source::filter_map f; + f.put("xx", "xxx"); + ASSERT_EQUAL(1U, f.size()); + e.a.connection().open_sender("x", sender_options().source(source_options().filters(f))); + + f.clear(); + f.put("yy", "yyy"); + ASSERT_EQUAL(1U, f.size()); + e.a.connection().open_receiver("y", receiver_options().source(source_options().filters(f))); + + while (ha.senders.size()+ha.receivers.size() < 2 || + hb.senders.size()+hb.receivers.size() < 2) + e.process(); + + proton::sender ax = quick_pop(ha.senders); + proton::receiver ay = quick_pop(ha.receivers); + proton::receiver bx = quick_pop(hb.receivers); + proton::sender by = quick_pop(hb.senders); + + // C++ binding only gives remote_source so only look at remote ends of links + f = by.source().filters(); + ASSERT_EQUAL(1U, f.size()); + ASSERT_EQUAL(value("yyy"), f.get("yy")); + + f = bx.source().filters(); + ASSERT_EQUAL(1U, f.size()); + ASSERT_EQUAL(value("xxx"), bx.source().filters().get("xx")); +} + + } int main(int, char**) { int failed = 0; + RUN_TEST(failed, test_link_filters()); RUN_TEST(failed, test_driver_link_id()); RUN_TEST(failed, test_endpoint_close()); RUN_TEST(failed, test_driver_disconnected()); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/include/test_bits.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/include/test_bits.hpp b/proton-c/bindings/cpp/src/include/test_bits.hpp index 0cfbe1f..79a0a17 100644 --- a/proton-c/bindings/cpp/src/include/test_bits.hpp +++ b/proton-c/bindings/cpp/src/include/test_bits.hpp @@ -54,6 +54,7 @@ inline void assert_equalish(T want, T got, T delta, const std::string& what) test::assert_equal((WANT), (GOT), FAIL_MSG("failed ASSERT_EQUAL(" #WANT ", " #GOT ")")) #define ASSERT_EQUALISH(WANT, GOT, DELTA) \ test::assert_equalish((WANT), (GOT), (DELTA), FAIL_MSG("failed ASSERT_EQUALISH(" #WANT ", " #GOT ")")) +#define ASSERT_THROWS(WANT, EXPR) do { try { EXPR; FAIL("Expected " #WANT); } catch(const WANT&) {} } while(0) #define RUN_TEST(BAD_COUNT, TEST) \ do { \ http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/map.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/map.cpp b/proton-c/bindings/cpp/src/map.cpp new file mode 100644 index 0000000..6fc80fa --- /dev/null +++ b/proton-c/bindings/cpp/src/map.cpp @@ -0,0 +1,254 @@ +/* + * + * 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 "proton/map.hpp" + +#include "proton/annotation_key.hpp" +#include "proton/scalar.hpp" +#include "proton/value.hpp" +#include "proton/codec/decoder.hpp" +#include "proton/codec/encoder.hpp" +#include "proton/codec/map.hpp" + +#include <map> +#include <string> + +// IMPLEMENTATION NOTES: +// - either value_, map_ or both can hold the up-to-date data +// - avoid encoding or decoding between map_ and value_ unless necessary +// - if (map.get()) then map_ is up to date +// - if (!value_.empty()) then value_ is up to date +// - if (map.get_() && !value_.empty()) then map_ and value_ have equivalent data +// - if (!map.get_() && value_.empty()) then that's equivalent to an empty map +// - cache() ensures that *map_ is up to date +// - flush() ensures value_ is up to date + +namespace proton { + +// use std::map as the actual map implementation type +template <class K, class T> +class map_type_impl : public std::map<K, T> {}; + +template <class K, class T> +map<K,T>::map() {} + +template <class K, class T> +map<K,T>::map(const map& x) { *this = x; } + +template <class K, class T> +map<K,T>::map(pn_data_t *d) : value_(d) {} + +template <class K, class T> +PN_CPP_EXTERN void swap(map<K,T>& x, map<K,T>& y) { + using namespace std; + swap(x.map_, y.map_); + swap(x.value_, y.value_); +} + +template <class K, class T> +void map<K,T>::ensure() const { + if (!map_) { + map_.reset(new map<K,T>::map_type); + } +} + +template <class K, class T> +map<K,T>& map<K,T>::operator=(const map& x) { + if (&x != this) { + if (!x.value_.empty()) { + map_.reset(); + value_ = x.value_; + } else if (x.map_.get()) { + value_.clear(); + ensure(); + *map_ = *x.map_; + } else { + clear(); + } + } + return *this; +} + +#if PN_CPP_HAS_RVALUE_REFERENCES +template <class K, class T> +map<K,T>::map(map&& x) : + map_(std::move(x.map_)), value_(std::move(x.value_)) {} + +template <class K, class T> +map<K,T>& map<K,T>::operator=(map&& x) { + if (&x != this) { + map_.reset(x.map_.release()); + value_ = std::move(x.value_); + } + return *this; +} +#endif + +template <class K, class T> +map<K,T>::~map() {} + +// Make sure map_ is valid +template <class K, class T> +const typename map<K,T>::map_type& map<K,T>::cache() const { + if (!map_) { + ensure(); + if (!value_.empty()) { + proton::get(value_, *map_); + } + } + return *map_; +} + +// Make sure map_ is valid, and mark value_ invalid +template <class K, class T> +typename map<K,T>::map_type& map<K,T>::cache_update() { + cache(); + value_.clear(); + return *map_; +} + +template <class K, class T> +value& map<K,T>::flush() const { + if (value_.empty()) { + // Create an empty map if need be, value_ must hold a valid map (even if empty) + // it must not be an empty (NULL_TYPE) proton::value. + ensure(); + value_ = *map_; + } + return value_; +} + +template <class K, class T> +void map<K,T>::value(const proton::value& x) { + value_.clear(); + // Validate the value by decoding it into map_, throw if not a valid map value. + ensure(); + proton::get(x, *map_); +} + +template <class K, class T> +proton::value& map<K,T>::value() { return flush(); } + +template <class K, class T> +const proton::value& map<K,T>::value() const { return flush(); } + +template <class K, class T> +T map<K,T>::get(const K& k) const { + if (this->empty()) return T(); + typename map_type::const_iterator i = cache().find(k); + if (i == map_->end()) return T(); + return i->second; +} + +template <class K, class T> +void map<K,T>::put(const K& k, const T& v) { + cache_update()[k] = v; +} + +template <class K, class T> +size_t map<K,T>::erase(const K& k) { + if (this->empty()) { + return 0; + } else { + return cache_update().erase(k); + } +} + +template <class K, class T> +bool map<K,T>::exists(const K& k) const { + return this->empty() ? 0 : cache().find(k) != cache().end(); +} + +template <class K, class T> +size_t map<K,T>::size() const { + return this->empty() ? 0 : cache().size(); +} + +template <class K, class T> +void map<K,T>::clear() { + if (map_.get()) { + map_->clear(); + } + value_.clear(); +} + +template <class K, class T> +bool map<K,T>::empty() const { + if (map_.get()) { + return map_->empty(); + } + if (value_.empty()) { + return true; + } + // We must decode the non-empty value to see if it is an empty map. + return cache().empty(); +} + +// Point to a different underlying pn_data_t, no copy +template <class K, class T> +void map<K,T>::reset(pn_data_t *d) { + value_.reset(d); // Points to d, not copy of d. + map_.reset(); + // NOTE: for internal use. Don't verify that the data is valid here as that + // would forcibly decode message maps immediately, we want to decode on-demand. +} + +template <class K, class T> +PN_CPP_EXTERN proton::codec::decoder& operator>>(proton::codec::decoder& d, map<K,T>& m) +{ + // Decode to m.map_ rather than m.value_ to verify the data is of valid type. + m.value_.clear(); + m.ensure(); + d >> *m.map_; + return d; +} + +template <class K, class T> +PN_CPP_EXTERN proton::codec::encoder& operator<<(proton::codec::encoder& e, const map<K,T>& m) +{ + if (!m.value_.empty()) { + return e << m.value_; // Copy the value + } + // Encode the (possibly empty) map_. + m.ensure(); + return e << *(m.map_); +} + +// Force the necessary template instantiations so that the library exports the correct symbols +template class PN_CPP_CLASS_EXTERN map<std::string, scalar>; +typedef map<std::string, scalar> cm1; +template PN_CPP_EXTERN void swap<>(cm1&, cm1&); +template PN_CPP_EXTERN proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cm1& m); +template PN_CPP_EXTERN proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cm1& m); + +template class PN_CPP_CLASS_EXTERN map<annotation_key, value>; +typedef map<annotation_key, value> cm2; +template PN_CPP_EXTERN void swap<>(cm2&, cm2&); +template PN_CPP_EXTERN proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cm2& m); +template PN_CPP_EXTERN proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cm2& m); + +template class PN_CPP_CLASS_EXTERN map<symbol, value>; +typedef map<symbol, value> cm3; +template PN_CPP_EXTERN void swap<>(cm3&, cm3&); +template PN_CPP_EXTERN proton::codec::decoder& operator>> <>(proton::codec::decoder& d, cm3& m); +template PN_CPP_EXTERN proton::codec::encoder& operator<< <>(proton::codec::encoder& e, const cm3& m); + +} // namespace proton http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/map_test.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/map_test.cpp b/proton-c/bindings/cpp/src/map_test.cpp new file mode 100644 index 0000000..680ae5f --- /dev/null +++ b/proton-c/bindings/cpp/src/map_test.cpp @@ -0,0 +1,126 @@ +/* + * 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 "proton/map.hpp" +#include "test_bits.hpp" + +#include <string> +#include <vector> + +namespace { + +using namespace std; +using namespace proton; + +void test_empty() { + proton::map<string, scalar> m; + ASSERT_EQUAL(0U, m.size()); + ASSERT(m.empty()); + ASSERT_EQUAL(0U, m.erase("x")); + ASSERT(!m.exists("x")); + + std::map<string, scalar> sm; + proton::get(m, sm); + ASSERT(sm.empty()); +} + +void test_use() { + proton::map<string, scalar> m; + + m.put("x", "y"); + ASSERT_EQUAL(scalar("y"), m.get("x")); + ASSERT(!m.empty()); + ASSERT(m.exists("x")); + ASSERT_EQUAL(1U, m.size()); + + m.put("a", "b"); + ASSERT_EQUAL(scalar("b"), m.get("a")); + ASSERT_EQUAL(2U, m.size()); + + ASSERT_EQUAL(1U, m.erase("x")); + ASSERT_EQUAL(1U, m.size()); + ASSERT(!m.exists("x")); + ASSERT_EQUAL(scalar("b"), m.get("a")); + + m.clear(); + ASSERT(m.empty()); +} + +void test_cppmap() { + std::map<string, scalar> sm; + sm["a"] = 2; + sm["b"] = 3; + proton::map<string, scalar> m; + m = sm; + ASSERT_EQUAL(scalar(2), m.get("a")); + ASSERT_EQUAL(scalar(3), m.get("b")); + ASSERT_EQUAL(2U, m.size()); + + std::map<string, scalar> sm2; + proton::get(m, sm2); + ASSERT_EQUAL(2U, sm2.size()); + ASSERT_EQUAL(scalar(2), sm2["a"]); + ASSERT_EQUAL(scalar(3), sm2["b"]); + + // Round trip: + value v = m.value(); + proton::map<string, scalar> m2; + m2.value(v); + + // Use a vector as map storage + vector<pair<string, scalar> > vm; + + vm.push_back(std::make_pair(string("x"), 8)); + vm.push_back(std::make_pair(string("y"), 9)); + m.value(vm); // Can't use type-safe op=, not enabled + ASSERT_EQUAL(scalar(8), m.get("x")); + ASSERT_EQUAL(scalar(9), m.get("y")); + ASSERT_EQUAL(2U, m.size()); + + vm.clear(); + proton::get(m, vm); + ASSERT_EQUAL(2U, vm.size()); + ASSERT_EQUAL(string("x"), vm[0].first); + ASSERT_EQUAL(scalar(8), vm[0].second); +} + +void test_value() { + proton::map<string, scalar> m; + value v; + v = "foo"; + ASSERT_THROWS(conversion_error, m.value(v)); + std::map<int, float> bad; + // Wrong type of map. + // Note we can't detect an empty map of bad type because AMQP maps allow + // mixed types, so there must be data to object to. + bad[1]=1.0; + ASSERT_THROWS(conversion_error, m.value(bad)); +} + +} + +int main(int, char**) { + int failed = 0; + RUN_TEST(failed, test_empty()); + RUN_TEST(failed, test_use()); + RUN_TEST(failed, test_cppmap()); + RUN_TEST(failed, test_value()); + return failed; +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/message.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/message.cpp b/proton-c/bindings/cpp/src/message.cpp index df7fae1..eb0695e 100644 --- a/proton-c/bindings/cpp/src/message.cpp +++ b/proton-c/bindings/cpp/src/message.cpp @@ -42,31 +42,32 @@ namespace proton { struct message::impl { - value body_; - property_map application_properties_; - annotation_map message_annotations_; - annotation_map delivery_annotations_; + value body; + property_map properties; + annotation_map annotations; + annotation_map instructions; impl(pn_message_t *msg) { - body_.reset(pn_message_body(msg)); + body.reset(pn_message_body(msg)); + properties.reset(pn_message_properties(msg)); + annotations.reset(pn_message_annotations(msg)); + instructions.reset(pn_message_instructions(msg)); } void clear() { - application_properties_.clear(); - message_annotations_.clear(); - delivery_annotations_.clear(); + properties.clear(); + annotations.clear(); + instructions.clear(); } - friend void swap(impl& x, impl& y) { - using std::swap; - swap(x.body_, y.body_); - swap(x.application_properties_, y.application_properties_); - swap(x.message_annotations_, y.message_annotations_); - swap(x.delivery_annotations_, y.delivery_annotations_); - } + // Encode cached maps back to the underlying pn_data_t + void flush() { + properties.value(); + annotations.value(); + instructions.value(); + } }; - message::message() : pn_msg_(0) {} message::message(const message &m) : pn_msg_(0) { *this = m; } @@ -88,9 +89,7 @@ message::~message() { } void swap(message& x, message& y) { - using std::swap; - swap(x.pn_msg_, y.pn_msg_); - swap(x.impl(), y.impl()); + std::swap(x.pn_msg_, y.pn_msg_); } pn_message_t *message::pn_msg() const { @@ -116,7 +115,12 @@ message& message::operator=(const message& m) { return *this; } -void message::clear() { if (pn_msg_) pn_message_clear(pn_msg_); } +void message::clear() { + if (pn_msg_) { + impl().clear(); + pn_message_clear(pn_msg_); + } +} namespace { void check(int err) { @@ -238,65 +242,35 @@ void message::inferred(bool b) { pn_message_set_inferred(pn_msg(), b); } void message::body(const value& x) { body() = x; } -const value& message::body() const { return impl().body_; } -value& message::body() { return impl().body_; } - -// MAP CACHING: the properties and annotations maps can either be encoded in the -// pn_message pn_data_t structures OR decoded as C++ map members of the message -// but not both. At least one of the pn_data_t or the map member is always -// empty, the non-empty one is the authority. - -// Decode a map on demand -template<class M, class F> M& get_map(pn_message_t* msg, F get, M& map) { - codec::decoder d(make_wrapper(get(msg))); - if (map.empty() && !d.empty()) { - d.rewind(); - d >> map; - d.clear(); // The map member is now the authority. - } - return map; -} - -// Encode a map if necessary. -template<class M, class F> M& put_map(pn_message_t* msg, F get, M& map) { - codec::encoder e(make_wrapper(get(msg))); - if (e.empty() && !map.empty()) { - e << map; - map.clear(); // The encoded pn_data_t is now the authority. - } - return map; -} +const value& message::body() const { return impl().body; } +value& message::body() { return impl().body; } message::property_map& message::properties() { - return get_map(pn_msg(), pn_message_properties, impl().application_properties_); + return impl().properties; } const message::property_map& message::properties() const { - return get_map(pn_msg(), pn_message_properties, impl().application_properties_); + return impl().properties; } - message::annotation_map& message::message_annotations() { - return get_map(pn_msg(), pn_message_annotations, impl().message_annotations_); + return impl().annotations; } const message::annotation_map& message::message_annotations() const { - return get_map(pn_msg(), pn_message_annotations, impl().message_annotations_); + return impl().annotations; } - message::annotation_map& message::delivery_annotations() { - return get_map(pn_msg(), pn_message_instructions, impl().delivery_annotations_); + return impl().instructions; } const message::annotation_map& message::delivery_annotations() const { - return get_map(pn_msg(), pn_message_instructions, impl().delivery_annotations_); + return impl().instructions; } void message::encode(std::vector<char> &s) const { - put_map(pn_msg(), pn_message_properties, impl().application_properties_); - put_map(pn_msg(), pn_message_annotations, impl().message_annotations_); - put_map(pn_msg(), pn_message_instructions, impl().delivery_annotations_); + impl().flush(); size_t sz = std::max(s.capacity(), size_t(512)); while (true) { s.resize(sz); http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/message_test.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/message_test.cpp b/proton-c/bindings/cpp/src/message_test.cpp index 4d4e239..eafea2e 100644 --- a/proton-c/bindings/cpp/src/message_test.cpp +++ b/proton-c/bindings/cpp/src/message_test.cpp @@ -139,14 +139,16 @@ void test_message_maps() { m.properties().put("foo", 12); m.delivery_annotations().put("bar", "xyz"); - m.message_annotations().put(23, "23"); + ASSERT_EQUAL(m.properties().get("foo"), scalar(12)); ASSERT_EQUAL(m.delivery_annotations().get("bar"), scalar("xyz")); ASSERT_EQUAL(m.message_annotations().get(23), scalar("23")); message m2(m); + ASSERT_EQUAL(m.properties().get("foo"), scalar(12)); // Decoding shouldn't change it + ASSERT_EQUAL(m2.properties().get("foo"), scalar(12)); ASSERT_EQUAL(m2.delivery_annotations().get("bar"), scalar("xyz")); ASSERT_EQUAL(m2.message_annotations().get(23), scalar("23")); @@ -155,13 +157,14 @@ void test_message_maps() { m.delivery_annotations().put(24, 1000); m.message_annotations().erase(23); - m2 = m; - ASSERT_EQUAL(1u, m2.properties().size()); - ASSERT_EQUAL(m2.properties().get("foo"), scalar("newfoo")); - ASSERT_EQUAL(2u, m2.delivery_annotations().size()); - ASSERT_EQUAL(m2.delivery_annotations().get("bar"), scalar("xyz")); - ASSERT_EQUAL(m2.delivery_annotations().get(24), scalar(1000)); - ASSERT(m2.message_annotations().empty()); + message m3 = m; + size_t size = m3.properties().size(); + ASSERT_EQUAL(1u, size); + ASSERT_EQUAL(m3.properties().get("foo"), scalar("newfoo")); + ASSERT_EQUAL(2u, m3.delivery_annotations().size()); + ASSERT_EQUAL(m3.delivery_annotations().get("bar"), scalar("xyz")); + ASSERT_EQUAL(m3.delivery_annotations().get(24), scalar(1000)); + ASSERT(m3.message_annotations().empty()); } } http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/node_options.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/node_options.cpp b/proton-c/bindings/cpp/src/node_options.cpp index 5bb2f8e..894ce9c 100644 --- a/proton-c/bindings/cpp/src/node_options.cpp +++ b/proton-c/bindings/cpp/src/node_options.cpp @@ -103,8 +103,7 @@ class source_options::impl { pn_terminus_set_distribution_mode(unwrap(s), pn_distribution_mode_t(distribution_mode.value)); if (filters.set && !filters.value.empty()) { // Applied at most once via source_option. No need to clear. - codec::encoder e(make_wrapper(pn_terminus_filter(unwrap(s)))); - e << filters.value; + value(pn_terminus_filter(unwrap(s))) = filters.value; } } }; http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/scalar_base.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/scalar_base.cpp b/proton-c/bindings/cpp/src/scalar_base.cpp index 840a6e8..0d66cdc 100644 --- a/proton-c/bindings/cpp/src/scalar_base.cpp +++ b/proton-c/bindings/cpp/src/scalar_base.cpp @@ -147,8 +147,7 @@ bool operator<(const scalar_base& x, const scalar_base& y) { std::ostream& operator<<(std::ostream& o, const scalar_base& s) { switch (s.type()) { - case NULL_TYPE: return o; // NULL is empty, doesn't print (like empty string) - // Print byte types as integer, not char. + case NULL_TYPE: return o << "<null>"; case BYTE: return o << static_cast<int>(internal::get<int8_t>(s)); case UBYTE: return o << static_cast<unsigned int>(internal::get<uint8_t>(s)); // Other types printed using normal C++ operator << http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/source.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/source.cpp b/proton-c/bindings/cpp/src/source.cpp index a407d0b..524428f 100644 --- a/proton-c/bindings/cpp/src/source.cpp +++ b/proton-c/bindings/cpp/src/source.cpp @@ -30,11 +30,21 @@ namespace proton { // Set parent_ non-null when the local terminus is authoritative and may need to be looked up. -source::source(pn_terminus_t *t) : terminus(make_wrapper(t)) {} +source::source(pn_terminus_t *t) : terminus(make_wrapper(t)), + filters_(pn_terminus_filter(object_)) +{} -source::source(const sender& snd) : terminus(make_wrapper(pn_link_remote_source(unwrap(snd)))) { parent_ = unwrap(snd); } +source::source(const sender& snd) : + terminus(make_wrapper(pn_link_remote_source(unwrap(snd)))), + filters_(pn_terminus_filter(object_)) +{ + parent_ = unwrap(snd); +} -source::source(const receiver& rcv) : terminus(make_wrapper(pn_link_remote_source(unwrap(rcv)))) {} +source::source(const receiver& rcv) : + terminus(make_wrapper(pn_link_remote_source(unwrap(rcv)))), + filters_(pn_terminus_filter(object_)) +{} std::string source::address() const { pn_terminus_t *authoritative = object_; @@ -47,14 +57,8 @@ enum source::distribution_mode source::distribution_mode() const { return (enum distribution_mode)pn_terminus_get_distribution_mode(object_); } -source::filter_map source::filters() const { - codec::decoder d(make_wrapper(pn_terminus_filter(object_))); - filter_map map; - if (!d.empty()) { - d.rewind(); - d >> map; - } - return map; +const source::filter_map& source::filters() const { + return filters_; } } http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/864519dd/proton-c/bindings/cpp/src/value.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/value.cpp b/proton-c/bindings/cpp/src/value.cpp index edf9074..03fc224 100644 --- a/proton-c/bindings/cpp/src/value.cpp +++ b/proton-c/bindings/cpp/src/value.cpp @@ -183,6 +183,9 @@ bool operator<(const value& x, const value& y) { } std::ostream& operator<<(std::ostream& o, const value& x) { + if (x.empty()) { + return o << "<empty-value>"; + } if (type_id_is_scalar(x.type()) || x.empty()) return o << proton::get<scalar>(x); // Print as a scalar // Use pn_inspect for complex types. --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
