topic_matcher.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /////////////////////////////////////////////////////////////////////////////
  2. /// @file topic_matcher.h
  3. /// Declaration of MQTT topic_matcher class
  4. /// @date April 23, 2022
  5. /// @author Frank Pagliughi
  6. /////////////////////////////////////////////////////////////////////////////
  7. /*******************************************************************************
  8. * Copyright (c) 2022 Frank Pagliughi <fpagliughi@mindspring.com>
  9. *
  10. * All rights reserved. This program and the accompanying materials
  11. * are made available under the terms of the Eclipse Public License v2.0
  12. * and Eclipse Distribution License v1.0 which accompany this distribution.
  13. *
  14. * The Eclipse Public License is available at
  15. * http://www.eclipse.org/legal/epl-v20.html
  16. * and the Eclipse Distribution License is available at
  17. * http://www.eclipse.org/org/documents/edl-v10.php.
  18. *
  19. * Contributors:
  20. * Frank Pagliughi - initial implementation and documentation
  21. *******************************************************************************/
  22. #ifndef __mqtt_topic_matcher_h
  23. #define __mqtt_topic_matcher_h
  24. #include "mqtt/types.h"
  25. #include "mqtt/topic.h"
  26. #include <vector>
  27. #include <map>
  28. #include <forward_list>
  29. #include <initializer_list>
  30. #include <memory>
  31. namespace mqtt {
  32. /////////////////////////////////////////////////////////////////////////////
  33. /**
  34. * This can be used to get an iterator to all items that have a filter that
  35. * matches a topic. To test against a single filter, see
  36. * [`TopicFilter`](crate::TopicFilter). This collection is more commonly
  37. * used when there are a number of filters and each needs to be associated
  38. * with a particular action or piece of data. Note, though, that a single
  39. * incoming topic could match against several items in the collection. For
  40. * example, the topic:
  41. * data/temperature/engine
  42. *
  43. * Could match against the filters:
  44. * data/temperature/# data/+/engine
  45. *
  46. * Thus, the collection gives an iterator for the items matching a topic.
  47. *
  48. * A common use for this would be to store callbacks to process incoming
  49. * messages based on topics.
  50. *
  51. * This code was adapted from the Eclipse Python `MQTTMatcher` class:
  52. *
  53. <https://github.com/eclipse/paho.mqtt.python/blob/master/src/paho/mqtt/matcher.py>
  54. *
  55. * which use a prefix tree (trie) to store the values.
  56. *
  57. * For example, if you had a `topic_mapper<int>` and you inserted:
  58. * insert({"some/random/topic", 42})
  59. * insert({"some/#", 99})
  60. * insert({"some/+/topic", 33})
  61. *
  62. * The collection would be built like:
  63. * "some" -> <null>
  64. * "random" -> <null>
  65. * "topic" -> <42>
  66. * "#" -> <99>
  67. * "+" -> <null>
  68. * "topic" -> <33>
  69. */
  70. template <typename T>
  71. class topic_matcher
  72. {
  73. public:
  74. using key_type = string;
  75. using mapped_type = T;
  76. using value_type = std::pair<key_type, mapped_type>;
  77. using reference = value_type;
  78. using const_reference = const value_type&;
  79. private:
  80. /**
  81. * The nodes of the collection.
  82. */
  83. struct node
  84. {
  85. using ptr_t = std::unique_ptr<node>;
  86. using map_t = std::map<string, ptr_t>;
  87. /** The value that matches the topic at this node, if any */
  88. std::unique_ptr<value_type> content;
  89. /** Child nodes mapped by the next field of the topic */
  90. map_t children;
  91. static ptr_t create() {
  92. // TODO: When available (C++14,17) use
  93. // std::make_unique<node>();
  94. return std::unique_ptr<node>(new node{});
  95. }
  96. };
  97. using node_ptr = typename node::ptr_t;
  98. using node_map = typename node::map_t;
  99. /** The root node of the collection */
  100. node_ptr root_;
  101. public:
  102. class iterator {
  103. /**
  104. * Information about a node to search.
  105. */
  106. struct search_node {
  107. /** The current node being searched. null means end. */
  108. node* node_;
  109. /** The fields of the topic still to be searched. */
  110. std::forward_list<string> syms_;
  111. search_node() : node_(nullptr) {}
  112. search_node(node* nd, const std::forward_list<string>& sy)
  113. : node_(nd), syms_(sy) {}
  114. search_node(node* nd, std::forward_list<string>&& sy)
  115. : node_(nd), syms_(std::move(sy)) {}
  116. };
  117. /** The last-found value */
  118. value_type* pval_;
  119. /** The current search node */
  120. search_node snode_;
  121. /** The nodes still to be checked */
  122. std::vector<search_node> nodes_;
  123. /**
  124. * Move the next iterator to the next value, or to end(), if none
  125. * left.
  126. *
  127. * This will keep recursing until it finds a matching node that
  128. * contains a value or it reaches the end.
  129. */
  130. void next() {
  131. pval_ = nullptr;
  132. // Can't move if we're already at the end
  133. if (!snode_.node_)
  134. return;
  135. if (snode_.syms_.empty()) {
  136. pval_ = snode_.node_->content.get();
  137. }
  138. else {
  139. typename node_map::iterator child;
  140. auto map_end = snode_.node_->children.end();
  141. auto sym = snode_.syms_.front();
  142. if ((child = snode_.node_->children.find(sym)) != map_end) {
  143. auto syms = snode_.syms_;
  144. syms.pop_front();
  145. nodes_.push_back({child->second.get(), std::move(syms)});
  146. }
  147. if ((child = snode_.node_->children.find("+")) != map_end) {
  148. auto syms = snode_.syms_;
  149. syms.pop_front();
  150. nodes_.push_back({child->second.get(), std::move(syms)});
  151. }
  152. if ((child = snode_.node_->children.find("#")) != map_end) {
  153. pval_ = child->second->content.get();
  154. }
  155. }
  156. if (!nodes_.empty()) {
  157. // TODO: List pop_front()?
  158. snode_ = nodes_.back();
  159. nodes_.pop_back();
  160. if (!pval_) {
  161. // Recurse
  162. return this->next();
  163. }
  164. }
  165. else {
  166. snode_ = search_node();
  167. }
  168. }
  169. friend class topic_matcher;
  170. iterator() : pval_(nullptr) {}
  171. iterator(value_type* pval) : pval_(pval) {}
  172. iterator(node* root, const string& topic) : pval_(nullptr) {
  173. auto v = topic::split(topic);
  174. std::forward_list<string> syms(v.begin(), v.end());
  175. snode_ = search_node(root, std::move(syms));
  176. next();
  177. }
  178. public:
  179. /**
  180. * Gets a reference to the current value.
  181. * @return A reference to the current value.
  182. */
  183. reference operator*() noexcept {
  184. return *pval_;
  185. }
  186. /**
  187. * Gets a const reference to the current value.
  188. * @return A const reference to the current value.
  189. */
  190. const_reference operator*() const noexcept {
  191. return *pval_;
  192. }
  193. /**
  194. * Get a pointer to the current value.
  195. * @return A pointer to the current value.
  196. */
  197. value_type* operator->() noexcept {
  198. return pval_;
  199. }
  200. /**
  201. * Get a const pointer to the current value.
  202. * @return A const pointer to the current value.
  203. */
  204. const value_type* operator->() const noexcept {
  205. return pval_;
  206. }
  207. /**
  208. * Postfix increment operator.
  209. * @return An iterator pointing to the previous matching item.
  210. */
  211. iterator operator++(int) noexcept {
  212. auto tmp = *this;
  213. this->next();
  214. return tmp;
  215. }
  216. /**
  217. * Prefix increment operator.
  218. * @return An iterator pointing to the next matching item.
  219. */
  220. iterator& operator++() noexcept {
  221. this->next();
  222. return *this;
  223. }
  224. /**
  225. * Compares two iterators to see if they don't refer to the same
  226. * node.
  227. *
  228. * @param other The other iterator to compare against this one.
  229. * @return @em true if they don't match, @em false if they do
  230. */
  231. bool operator!=(const iterator& other) const noexcept {
  232. // TODO: Is this sufficient in the long run?
  233. return pval_ != other.pval_ || snode_.node_ != other.snode_.node_;
  234. }
  235. };
  236. /**
  237. * A const iterator.
  238. */
  239. class const_iterator : public iterator {
  240. using base = iterator;
  241. friend class topic_matcher;
  242. const_iterator(iterator it) : base(it) {}
  243. public:
  244. /**
  245. * Gets a const reference to the current value.
  246. * @return A const reference to the current value.
  247. */
  248. const_reference operator*() const noexcept {
  249. return base::operator*();
  250. }
  251. /**
  252. * Get a const pointer to the current value.
  253. * @return A const pointer to the current value.
  254. */
  255. const value_type* operator->() const noexcept {
  256. return base::operator->();
  257. }
  258. };
  259. /**
  260. * Creates new, empty collection.
  261. */
  262. topic_matcher()
  263. : root_(node::create()) {}
  264. /**
  265. * Creates a new collection with a list of key/value pairs.
  266. *
  267. * This can be used to create a connection from a table of entries, as
  268. * key/value pairs, like:
  269. *
  270. * topic_matcher<int> matcher {
  271. * { "#", -1 },
  272. * { "some/random/topic", 42 },
  273. * { "some/#", 99 }
  274. * }
  275. *
  276. * @param lst The list of key/value pairs to populate the collection.
  277. */
  278. topic_matcher(std::initializer_list<value_type> lst)
  279. : root_(node::create()) {
  280. for (const auto& v : lst) {
  281. insert(v);
  282. }
  283. }
  284. /**
  285. * Inserts a new key/value pair into the collection.
  286. * @param val The value to place in the collection.
  287. */
  288. void insert(value_type&& val) {
  289. auto nd = root_.get();
  290. auto fields = topic::split(val.first);
  291. for (auto& field : fields) {
  292. auto it = nd->children.find(field);
  293. if (it == nd->children.end()) {
  294. nd->children[field] = node::create();
  295. it = nd->children.find(field);
  296. }
  297. nd = it->second.get();
  298. }
  299. // TODO: When available (C++14,17) use:
  300. // std::make_unique<value_type>(std::move(val));
  301. nd->content = std::unique_ptr<value_type>(new value_type{std::move(val)});
  302. }
  303. /**
  304. * Inserts a new value into the collection.
  305. * @param key The topic/filter entry
  306. * @param val The value to associate with that entry.
  307. */
  308. void insert(const value_type& val) {
  309. value_type v { val };
  310. this->insert(std::move(v));
  311. }
  312. /**
  313. * Gets a pointer to the value at the requested key.
  314. * @param key The topic/filter entry to find.
  315. * @return An iterator to the value if found, @em end() if not found.
  316. */
  317. iterator find(const key_type& key) {
  318. auto nd = root_.get();
  319. auto fields = topic::split(key);
  320. for (auto& field : fields) {
  321. auto it = nd->children.find(field);
  322. if (it == nd->children.end())
  323. return end();
  324. nd = it->second.get();
  325. }
  326. return iterator{ nd->content.get() };
  327. }
  328. /**
  329. * Gets a const pointer to the value at the requested key.
  330. * @param key The topic/filter entry to find.
  331. * @return A const pointer to the value if found, @em nullptr if not
  332. * found.
  333. */
  334. const_iterator find(const key_type& key) const {
  335. return const_cast<topic_matcher*>(this)->find(key);
  336. }
  337. /**
  338. * Gets an iterator that can find the matches to the topic.
  339. * @param topic The topic to search for matches.
  340. * @return An iterator that can find the matches to the topic.
  341. */
  342. iterator matches(const string& topic) {
  343. return iterator(root_.get(), topic);
  344. }
  345. /**
  346. * Gets a const iterator that can find the matches to the topic.
  347. * @param topic The topic to search for matches.
  348. * @return A const iterator that can find the matches to the topic.
  349. */
  350. const_iterator matches(const string& topic) const {
  351. return iterator(root_.get(), topic);
  352. }
  353. /**
  354. * Gets an iterator for the end of the collection.
  355. *
  356. * This simply returns an empty/null iterator which we can use to signal
  357. * the end of the collection.
  358. *
  359. * @return An empty/null iterator indicating the end of the collection.
  360. */
  361. const_iterator end() const noexcept {
  362. return iterator {};
  363. }
  364. /**
  365. * Gets an iterator for the end of the collection.
  366. *
  367. * This simply returns an empty/null iterator which we can use to signal
  368. * the end of the collection.
  369. *
  370. * @return An empty/null iterator indicating the end of the collection.
  371. */
  372. const_iterator cend() const noexcept {
  373. return iterator {};
  374. }
  375. };
  376. /////////////////////////////////////////////////////////////////////////////
  377. // end namespace mqtt
  378. }
  379. #endif // __mqtt_topic_matcher_h