palimpsest — Fast serializable C++ dictionaries  v2.2.0
Dictionary.cpp
Go to the documentation of this file.
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright 2022 Stéphane Caron
3 // Copyright 2024 Inria
4 /*
5  * This file incorporates work covered by the following copyright and
6  * permission notice:
7  *
8  * Configuration and DataStore classes of mc_rtc
9  * Copyright 2015-2020 CNRS-UM LIRMM, CNRS-AIST JRL
10  * SPDX-License-Identifier: BSD-2-Clause
11  */
12 
13 #include "palimpsest/Dictionary.h"
14 
15 #include <fstream>
16 #include <memory>
17 #include <string>
18 #include <vector>
19 
20 #include "palimpsest/exceptions/KeyError.h"
21 #include "palimpsest/exceptions/TypeError.h"
22 #include "palimpsest/mpack/eigen.h"
23 
24 namespace palimpsest {
25 
26 using exceptions::KeyError;
27 using exceptions::TypeError;
28 using mpack::mpack_node_matrix3d;
29 using mpack::mpack_node_quaterniond;
30 using mpack::mpack_node_vector2d;
31 using mpack::mpack_node_vector3d;
32 using mpack::mpack_node_vectorXd;
33 
34 void Dictionary::clear() noexcept {
35  assert(this->is_map());
36  map_.clear();
37 }
38 
39 void Dictionary::update(const char *data, size_t size) {
40  mpack_tree_t tree;
41  mpack_tree_init_data(&tree, data, size);
42  mpack_tree_parse(&tree);
43  const auto status = mpack_tree_error(&tree);
44  if (status != mpack_ok) {
45  spdlog::error("MPack tree error: \"{}\", skipping Dictionary::update",
46  mpack_error_to_string(status));
47  return;
48  }
49  update(mpack_tree_root(&tree));
50  mpack_tree_destroy(&tree);
51 }
52 
53 void Dictionary::update(mpack_node_t node) {
54  if (mpack_node_type(node) == mpack_type_nil) {
55  return;
56  }
57 
58  if (this->is_value()) {
59  value_.deserialize(node);
60  return;
61  }
62 
63  /* Now we have asserted that this->is_map() */
64  if (mpack_node_type(node) != mpack_type_map) {
65  throw TypeError(__FILE__, __LINE__,
66  std::string("Expecting a map, not ") +
67  mpack_type_to_string(mpack_node_type(node)));
68  }
69 
70  for (size_t i = 0; i < mpack_node_map_count(node); ++i) {
71  const mpack_node_t key_node = mpack_node_map_key_at(node, i);
72  const mpack_node_t value_node = mpack_node_map_value_at(node, i);
73  const std::string key = {mpack_node_str(key_node),
74  mpack_node_strlen(key_node)};
75  auto it = map_.find(key);
76  if (it == map_.end()) {
77  this->insert_at_key_(key, value_node);
78  } else /* (it != map_.end()) */ {
79  try {
80  it->second->update(value_node);
81  } catch (const TypeError &e) {
82  throw TypeError(e, " ← at key \"" + key + "\"");
83  }
84  }
85  }
86 }
87 
88 void Dictionary::insert_at_key_(const std::string &key,
89  const mpack_node_t &value) {
90  switch (mpack_node_type(value)) {
91  case mpack_type_bool:
92  this->insert<bool>(key, mpack_node_bool(value));
93  break;
94  case mpack_type_int:
95  this->insert<int>(key, mpack_node_int(value));
96  break;
97  case mpack_type_uint:
98  this->insert<unsigned>(key, mpack_node_uint(value));
99  break;
100  case mpack_type_float:
101  this->insert<float>(key, mpack_node_float(value));
102  break;
103  case mpack_type_double:
104  this->insert<double>(key, mpack_node_double(value));
105  break;
106  case mpack_type_str:
107  this->insert<std::string>(
108  key, std::string{mpack_node_str(value), mpack_node_strlen(value)});
109  break;
110  case mpack_type_array: {
111  size_t length = mpack_node_array_length(value);
112  if (length == 0) {
113  throw TypeError(__FILE__, __LINE__,
114  std::string("Cannot deserialize an empty list "
115  "(precludes type inference) at key \"") +
116  key + "\"");
117  }
118  mpack_node_t first_item = mpack_node_array_at(value, 0);
119  mpack_type_t array_type = mpack_node_type(first_item);
120  if (array_type == mpack_type_double) {
121  switch (length) {
122  case 2:
123  this->insert<Eigen::Vector2d>(key, mpack_node_vector2d(value));
124  break;
125  case 3:
126  this->insert<Eigen::Vector3d>(key, mpack_node_vector3d(value));
127  break;
128  case 4:
129  this->insert<Eigen::Quaterniond>(key,
130  mpack_node_quaterniond(value));
131  break;
132  case 9:
133  this->insert<Eigen::Matrix3d>(key, mpack_node_matrix3d(value));
134  break;
135  default:
136  this->insert<Eigen::VectorXd>(key, mpack_node_vectorXd(value));
137  break;
138  }
139  } else if (array_type == mpack_type_array) {
140  // We only handle lists of Eigen::VectorXd vectors for now
141  auto &new_vec_vec =
142  this->insert<std::vector<Eigen::VectorXd>>(key, length);
143  for (unsigned index = 0; index < length; ++index) {
144  mpack_node_t sub_array = mpack_node_array_at(value, index);
145  mpack_type_t sub_type = mpack_node_type(sub_array);
146  if (sub_type != mpack_type_array) {
147  throw TypeError(__FILE__, __LINE__,
148  std::string("Encountered non-array item ") +
149  mpack_type_to_string(sub_type) +
150  " while parsing array of arrays at key \"" +
151  key + "\"");
152  }
153  unsigned sub_length = mpack_node_array_length(sub_array);
154  Eigen::VectorXd &vector = new_vec_vec[index];
155  vector.resize(sub_length);
156  for (Eigen::Index j = 0; j < sub_length; ++j) {
157  vector(j) = mpack_node_double(mpack_node_array_at(sub_array, j));
158  }
159  }
160  } else {
161  throw TypeError(__FILE__, __LINE__,
162  std::string("Unsupported array of ") +
163  mpack_type_to_string(array_type) +
164  " elements encountered at key \"" + key + "\"");
165  }
166  break;
167  }
168  case mpack_type_map:
169  this->operator()(key).update(value);
170  break;
171  case mpack_type_bin:
172  case mpack_type_nil:
173  default:
174  throw TypeError(__FILE__, __LINE__,
175  std::string("Cannot insert values of type ") +
176  mpack_type_to_string(mpack_node_type(value)));
177  }
178 } // namespace palimpsest
179 
180 std::vector<std::string> Dictionary::keys() const noexcept {
181  std::vector<std::string> out;
182  out.reserve(map_.size());
183  for (const auto &key_child : map_) {
184  out.push_back(key_child.first);
185  }
186  return out;
187 }
188 
189 void Dictionary::remove(const std::string &key) noexcept {
190  auto it = map_.find(key);
191  if (it == map_.end()) {
192  spdlog::error("[Dictionary::remove] No key to remove at \"{}\"", key);
193  return;
194  }
195  map_.erase(it);
196 }
197 
198 Dictionary &Dictionary::operator()(const std::string &key) {
199  if (this->is_value()) {
200  throw TypeError(__FILE__, __LINE__,
201  "Cannot look up at key \"" + key +
202  "\" in non-dictionary object of type \"" +
203  value_.type_name() + "\".");
204  }
205  auto [it, _] = map_.try_emplace(key, std::make_unique<Dictionary>());
206  return *it->second;
207 }
208 
209 const Dictionary &Dictionary::operator()(const std::string &key) const {
210  if (this->is_value()) {
211  throw TypeError(__FILE__, __LINE__,
212  "Cannot lookup at key \"" + key +
213  "\" in non-dictionary object of type \"" +
214  value_.type_name() + "\".");
215  }
216  const auto it = map_.find(key);
217  if (it == map_.end()) {
218  throw KeyError(key, __FILE__, __LINE__,
219  "Since the dictionary is const it cannot be created.");
220  }
221  return *it->second;
222 }
223 
224 void Dictionary::read(const std::string &filename) {
225  std::ifstream input;
226  input.open(filename, std::ifstream::binary | std::ios::ate);
227  std::streamsize size = input.tellg();
228  input.seekg(0, std::ios::beg);
229  std::vector<char> buffer(size);
230  input.read(buffer.data(), size);
231  input.close();
232  this->update(buffer.data(), size);
233 }
234 
235 void Dictionary::write(const std::string &filename) const {
236  std::vector<char> buffer;
237  size_t size = this->serialize(buffer);
238 
239  std::ofstream output;
240  output.open(filename, std::ofstream::binary);
241  output.write(buffer.data(), static_cast<int>(size));
242  output.close();
243 }
244 
245 size_t Dictionary::serialize(std::vector<char> &buffer) const {
246  mpack::Writer writer(buffer);
247  serialize_(writer);
248  return writer.finish();
249 }
250 
251 void Dictionary::serialize_(mpack::Writer &writer) const {
252  if (this->is_value()) {
253  value_.serialize(writer);
254  return;
255  }
256  size_t size = map_.size();
257  writer.start_map(size);
258  for (const auto &key_child : map_) {
259  const auto &key = key_child.first;
260  const auto &child = *key_child.second;
261  writer.write(key);
262  child.serialize_(writer);
263  }
264  writer.finish_map();
265 }
266 
267 const Dictionary::Value &Dictionary::get_child_value_(
268  const std::string &key) const {
269  const auto it = map_.find(key);
270  if (it == map_.end()) {
271  throw KeyError(key, __FILE__, __LINE__, "");
272  } else if (!it->second->is_value()) {
273  throw TypeError(__FILE__, __LINE__,
274  "Child at key \"" + key + "\" is not a value");
275  }
276  return it->second->value_;
277 }
278 
279 std::ostream &operator<<(std::ostream &stream, const Dictionary &dict) {
280  if (dict.is_empty()) {
281  stream << "{}";
282  } else if (dict.is_value()) {
283  dict.value_.print(stream);
284  } else /* (dict.is_map()) */ {
285  stream << "{";
286  bool is_first = true;
287  for (const auto &key_child : dict.map_) {
288  const auto &key = key_child.first;
289  const auto &child = key_child.second;
290  if (is_first) {
291  is_first = false;
292  } else /* is not first key */ {
293  stream << ", ";
294  }
295  stream << "\"" << key << "\": " << *child;
296  }
297  stream << "}";
298  }
299  return stream;
300 }
301 
302 } // namespace palimpsest
Dictionary of values and sub-dictionaries.
Definition: Dictionary.h:97
Value value_
Internal value, used if we are a value.
Definition: Dictionary.h:709
std::unordered_map< std::string, std::unique_ptr< Dictionary > > map_
Key-value map, used if we are a map.
Definition: Dictionary.h:712
void read(const std::string &filename)
Update dictionary from a MessagePack binary file.
Definition: Dictionary.cpp:224
bool is_map() const noexcept
We are a (potentially empty) map if and only if the value is empty.
Definition: Dictionary.h:248
void update(const char *data, size_t size)
Update dictionary from raw MessagePack data.
Definition: Dictionary.cpp:39
unsigned size() const noexcept
Return the number of keys in the dictionary.
Definition: Dictionary.h:269
bool is_empty() const noexcept
We are empty if and only if we are a dictionary with no element.
Definition: Dictionary.h:251
void remove(const std::string &key) noexcept
Remove a key-value pair from the dictionary.
Definition: Dictionary.cpp:189
Dictionary & operator()(const std::string &key)
Return a reference to the dictionary at key, performing an insertion if such a key does not already e...
Definition: Dictionary.cpp:198
size_t serialize(std::vector< char > &buffer) const
Serialize to raw MessagePack data.
Definition: Dictionary.cpp:245
std::vector< std::string > keys() const noexcept
Return the list of keys of the dictionary.
Definition: Dictionary.cpp:180
void write(const std::string &filename) const
Write MessagePack serialization to a binary file.
Definition: Dictionary.cpp:235
void clear() noexcept
Remove all entries from the dictionary.
Definition: Dictionary.cpp:34
bool is_value() const noexcept
We are a value if and only if the internal value is non-empty.
Definition: Dictionary.h:254
std::ostream & operator<<(std::ostream &stream, const Dictionary &dict)
Definition: Dictionary.cpp:279