You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by as...@apache.org on 2017/06/02 22:51:20 UTC

[06/20] qpid-proton git commit: PROTON-1288: c++ provide convenient access to message/filter maps

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 <ac...@redhat.com>
Authored: Wed May 24 13:03:11 2017 -0400
Committer: Alan Conway <ac...@redhat.com>
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: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org