palimpsest v3.0.0
Loading...
Searching...
No Matches
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
14
15#include <cstring>
16#include <fstream>
17#include <memory>
18#include <string>
19#include <utility>
20#include <vector>
21
25#include "palimpsest/mpack/eigen.h"
26
27namespace palimpsest {
28
29using exceptions::KeyError;
30using exceptions::PalimpsestError;
31using exceptions::TypeError;
32using mpack::mpack_node_matrix3d;
33using mpack::mpack_node_quaterniond;
34using mpack::mpack_node_vector2d;
35using mpack::mpack_node_vector3d;
36using mpack::mpack_node_vectorXd;
37
38void Dictionary::clear() noexcept {
39 assert(this->is_map());
40 map_.clear();
41}
42
44 Dictionary result;
45 result.update(other);
46 return result;
47}
48
49void Dictionary::deserialize(const char *data, size_t size) {
50 mpack_tree_t tree;
51 mpack_tree_init_data(&tree, data, size);
52 mpack_tree_parse(&tree);
53 const auto status = mpack_tree_error(&tree);
54 if (status != mpack_ok) {
55 spdlog::error("MPack tree error: \"{}\", skipping Dictionary::deserialize",
56 mpack_error_to_string(status));
57 return;
58 }
59 deserialize_(mpack_tree_root(&tree));
60 mpack_tree_destroy(&tree);
61}
62
63void Dictionary::deserialize_(mpack_node_t node) {
64 if (mpack_node_type(node) == mpack_type_nil) {
65 return;
66 }
67
68 if (this->is_value()) {
69 value_.deserialize(node);
70 return;
71 }
72
73 /* Now we have asserted that this->is_map() */
74 if (mpack_node_type(node) != mpack_type_map) {
75 throw TypeError(__FILE__, __LINE__,
76 std::string("Expecting a map, not ") +
77 mpack_type_to_string(mpack_node_type(node)));
78 }
79
80 for (size_t i = 0; i < mpack_node_map_count(node); ++i) {
81 const mpack_node_t key_node = mpack_node_map_key_at(node, i);
82 const mpack_node_t value_node = mpack_node_map_value_at(node, i);
83 const std::string key = {mpack_node_str(key_node),
84 mpack_node_strlen(key_node)};
85 auto it = map_.find(key);
86 if (it == map_.end()) {
87 this->insert_at_key_(key, value_node);
88 } else /* (it != map_.end()) */ {
89 try {
90 it->second->deserialize_(value_node);
91 } catch (const TypeError &e) {
92 throw TypeError(e, " ← at key \"" + key + "\"");
93 }
94 }
95 }
96}
97
98void Dictionary::insert_at_key_(const std::string &key,
99 const mpack_node_t &value) {
100 switch (mpack_node_type(value)) {
101 case mpack_type_bool:
102 this->insert<bool>(key, mpack_node_bool(value));
103 break;
104 case mpack_type_int:
105 this->insert<int>(key, mpack_node_int(value));
106 break;
107 case mpack_type_uint:
108 this->insert<unsigned>(key, mpack_node_uint(value));
109 break;
110 case mpack_type_float:
111 this->insert<float>(key, mpack_node_float(value));
112 break;
113 case mpack_type_double:
114 this->insert<double>(key, mpack_node_double(value));
115 break;
116 case mpack_type_str:
117 this->insert<std::string>(
118 key, std::string{mpack_node_str(value), mpack_node_strlen(value)});
119 break;
120 case mpack_type_array: {
121 size_t length = mpack_node_array_length(value);
122 if (length == 0) {
123 throw TypeError(__FILE__, __LINE__,
124 std::string("Cannot deserialize an empty list "
125 "(precludes type inference) at key \"") +
126 key + "\"");
127 }
128 mpack_node_t first_item = mpack_node_array_at(value, 0);
129 mpack_type_t array_type = mpack_node_type(first_item);
130 if (array_type == mpack_type_double) {
131 switch (length) {
132 case 2:
133 this->insert<Eigen::Vector2d>(key, mpack_node_vector2d(value));
134 break;
135 case 3:
136 this->insert<Eigen::Vector3d>(key, mpack_node_vector3d(value));
137 break;
138 case 4:
139 this->insert<Eigen::Quaterniond>(key,
140 mpack_node_quaterniond(value));
141 break;
142 case 9:
143 this->insert<Eigen::Matrix3d>(key, mpack_node_matrix3d(value));
144 break;
145 default:
146 this->insert<Eigen::VectorXd>(key, mpack_node_vectorXd(value));
147 break;
148 }
149 } else if (array_type == mpack_type_array) {
150 // We only handle lists of Eigen::VectorXd vectors for now
151 auto &new_vec_vec =
152 this->insert<std::vector<Eigen::VectorXd>>(key, length);
153 for (unsigned index = 0; index < length; ++index) {
154 mpack_node_t sub_array = mpack_node_array_at(value, index);
155 mpack_type_t sub_type = mpack_node_type(sub_array);
156 if (sub_type != mpack_type_array) {
157 throw TypeError(__FILE__, __LINE__,
158 std::string("Encountered non-array item ") +
159 mpack_type_to_string(sub_type) +
160 " while parsing array of arrays at key \"" +
161 key + "\"");
162 }
163 unsigned sub_length = mpack_node_array_length(sub_array);
164 Eigen::VectorXd &vector = new_vec_vec[index];
165 vector.resize(sub_length);
166 for (Eigen::Index j = 0; j < sub_length; ++j) {
167 vector(j) = mpack_node_double(mpack_node_array_at(sub_array, j));
168 }
169 }
170 } else {
171 throw TypeError(__FILE__, __LINE__,
172 std::string("Unsupported array of ") +
173 mpack_type_to_string(array_type) +
174 " elements encountered at key \"" + key + "\"");
175 }
176 break;
177 }
178 case mpack_type_map:
179 this->operator()(key).deserialize_(value);
180 break;
181 case mpack_type_bin:
182 case mpack_type_nil:
183 default:
184 throw TypeError(__FILE__, __LINE__,
185 std::string("Cannot insert values of type ") +
186 mpack_type_to_string(mpack_node_type(value)));
187 }
188}
189
191 Dictionary result;
192
193 // If this is empty, no differences
194 if (this->is_empty()) {
195 return result;
196 }
197
198 // If this is a value
199 if (this->is_value()) {
200 if (other.is_value()) {
201 // Other is also a value, compare using serialization (for now)
202 std::vector<char> this_buffer, other_buffer;
203 size_t this_size = this->serialize(this_buffer);
204 size_t other_size = other.serialize(other_buffer);
205
206 // If values differ, return this value
207 if (this_size != other_size ||
208 std::memcmp(this_buffer.data(), other_buffer.data(), this_size) !=
209 0) {
210 result.value_ = this->value_;
211 }
212 } else {
213 // Other is not a value (empty or map), so this value is a difference
214 result.value_ = this->value_;
215 }
216 return result;
217 }
218
219 // If this is a map
220 assert(this->is_map());
221
222 for (const auto &key_child : map_) {
223 const std::string &key = key_child.first;
224 const Dictionary &this_child = *key_child.second;
225
226 // Check if the key exists in other
227 auto other_it = other.map_.find(key);
228 if (other_it == other.map_.end()) {
229 // Key doesn't exist in other, so include entire subtree as difference
230 result(key).update(this_child);
231 } else {
232 // Key exists in other, recursively compute difference
233 const Dictionary &other_child = *other_it->second;
234 Dictionary child_diff = this_child.difference(other_child);
235
236 // Only include this key if there are actual differences
237 if (!child_diff.is_empty()) {
238 result(key) = std::move(child_diff);
239 }
240 }
241 }
242
243 return result;
244}
245
246std::vector<std::string> Dictionary::keys() const noexcept {
247 std::vector<std::string> out;
248 out.reserve(map_.size());
249 for (const auto &key_child : map_) {
250 out.push_back(key_child.first);
251 }
252 return out;
253}
254
255std::vector<std::pair<std::string, std::reference_wrapper<const Dictionary>>>
256Dictionary::items() const noexcept {
257 std::vector<std::pair<std::string, std::reference_wrapper<const Dictionary>>>
258 out;
259 out.reserve(map_.size());
260 for (const auto &key_child : map_) {
261 out.emplace_back(key_child.first, std::cref(*key_child.second));
262 }
263 return out;
264}
265
266std::vector<std::reference_wrapper<const Dictionary>> Dictionary::values()
267 const noexcept {
268 std::vector<std::reference_wrapper<const Dictionary>> out;
269 out.reserve(map_.size());
270 for (const auto &key_child : map_) {
271 out.emplace_back(std::cref(*key_child.second));
272 }
273 return out;
274}
275
276std::pair<std::string, Dictionary> Dictionary::popitem() {
277 if (this->is_value()) {
278 throw TypeError(
279 __FILE__, __LINE__,
280 std::string(
281 "Cannot pop an item from non-dictionary object of type \"") +
282 value_.type_name() + "\".");
283 }
284 if (map_.empty()) {
285 throw KeyError("", __FILE__, __LINE__, "popitem(): dictionary is empty");
286 }
287
288 // Get the first element from the map
289 auto it = map_.begin();
290 std::string key = it->first;
291
292 // Move value out before erasing it
293 Dictionary value = std::move(*it->second);
294 map_.erase(it);
295
296 return std::make_pair(std::move(key), std::move(value));
297}
298
299void Dictionary::remove(const std::string &key) noexcept {
300 auto it = map_.find(key);
301 if (it == map_.end()) {
302 spdlog::error("[Dictionary::remove] No key to remove at \"{}\"", key);
303 return;
304 }
305 map_.erase(it);
306}
307
308Dictionary &Dictionary::operator()(const std::string &key) {
309 if (this->is_value()) {
310 throw TypeError(__FILE__, __LINE__,
311 "Cannot look up at key \"" + key +
312 "\" in non-dictionary object of type \"" +
313 value_.type_name() + "\".");
314 }
315 auto [it, _] = map_.try_emplace(key, std::make_unique<Dictionary>());
316 return *it->second;
317}
318
319const Dictionary &Dictionary::operator()(const std::string &key) const {
320 if (this->is_value()) {
321 throw TypeError(__FILE__, __LINE__,
322 "Cannot lookup at key \"" + key +
323 "\" in non-dictionary object of type \"" +
324 value_.type_name() + "\".");
325 }
326 const auto it = map_.find(key);
327 if (it == map_.end()) {
328 throw KeyError(key, __FILE__, __LINE__,
329 "Since the dictionary is const it cannot be created.");
330 }
331 return *it->second;
332}
333
334void Dictionary::read(const std::string &filename) {
335 std::ifstream input;
336 input.open(filename, std::ifstream::binary | std::ios::ate);
337 std::streamsize size = input.tellg();
338 input.seekg(0, std::ios::beg);
339 std::vector<char> buffer(size);
340 input.read(buffer.data(), size);
341 input.close();
342 deserialize(buffer.data(), size);
343}
344
345void Dictionary::write(const std::string &filename) const {
346 std::vector<char> buffer;
347 size_t size = this->serialize(buffer);
348
349 std::ofstream output;
350 output.open(filename, std::ofstream::binary);
351 output.write(buffer.data(), static_cast<int>(size));
352 output.close();
353}
354
355size_t Dictionary::serialize(std::vector<char> &buffer) const {
356 mpack::Writer writer(buffer);
357 serialize_(writer);
358 return writer.finish();
359}
360
361void Dictionary::serialize_(mpack::Writer &writer) const {
362 if (this->is_value()) {
363 value_.serialize(writer);
364 return;
365 }
366 size_t size = map_.size();
367 writer.start_map(size);
368 for (const auto &key_child : map_) {
369 const auto &key = key_child.first;
370 const auto &child = *key_child.second;
371 writer.write(key);
372 child.serialize_(writer);
373 }
374 writer.finish_map();
375}
376
377const Value &Dictionary::get_child_value_(const std::string &key) const {
378 const auto it = map_.find(key);
379 if (it == map_.end()) {
380 throw KeyError(key, __FILE__, __LINE__, "");
381 } else if (!it->second->is_value()) {
382 throw TypeError(__FILE__, __LINE__,
383 "Child at key \"" + key + "\" is not a value");
384 }
385 return it->second->value_;
386}
387
388std::ostream &operator<<(std::ostream &stream, const Dictionary &dict) {
389 if (dict.is_empty()) {
390 stream << "{}";
391 } else if (dict.is_value()) {
392 dict.value_.print(stream);
393 } else /* (dict.is_map()) */ {
394 stream << "{";
395 bool is_first = true;
396 for (const auto &key_child : dict.map_) {
397 const auto &key = key_child.first;
398 const auto &child = key_child.second;
399 if (is_first) {
400 is_first = false;
401 } else /* is not first key */ {
402 stream << ", ";
403 }
404 stream << "\"" << key << "\": " << *child;
405 }
406 stream << "}";
407 }
408 return stream;
409}
410
411void Dictionary::update(const Dictionary &other) {
412 // If other is empty, nothing to do
413 if (other.is_empty()) {
414 return;
415 }
416
417 // If other is a single value, replace this dictionary entirely
418 if (other.is_value()) {
419 *this = Dictionary();
420 this->value_ = other.value_;
421 return;
422 }
423
424 // If this is currently a value, we need to convert it to a map
425 // to accommodate the incoming map structure
426 if (this->is_value()) {
427 *this = Dictionary(); // Clear value and become empty map
428 }
429
430 // At this point both are maps, we update recursively
431 assert(this->is_map() && other.is_map());
432
433 for (const auto &key_child : other.map_) {
434 const std::string &key = key_child.first;
435 const Dictionary &other_child = *key_child.second;
436
437 auto it = map_.find(key);
438 if (it == map_.end()) {
439 // Key doesn't exist in this dictionary, create new entry
440 map_[key] = std::make_unique<Dictionary>();
441 map_[key]->update(other_child);
442 } else {
443 // Key exists, replace it entirely
444 *it->second = Dictionary();
445 it->second->update(other_child);
446 }
447 }
448}
449
450} // namespace palimpsest
Dictionary of values and sub-dictionaries.
Definition: Dictionary.h:96
Value value_
Internal value, used if we are a value.
Definition: Dictionary.h:957
std::unordered_map< std::string, std::unique_ptr< Dictionary > > map_
Key-value map, used if we are a map.
Definition: Dictionary.h:960
void read(const std::string &filename)
Update dictionary from a MessagePack binary file.
Definition: Dictionary.cpp:334
bool is_map() const noexcept
We are a (potentially empty) map if and only if the value is empty.
Definition: Dictionary.h:121
void deserialize(const char *data, size_t size)
Update dictionary from raw MessagePack data.
Definition: Dictionary.cpp:49
unsigned size() const noexcept
Return the number of keys in the dictionary.
Definition: Dictionary.h:207
std::pair< std::string, Dictionary > popitem()
Remove and return a (key, value) pair from the dictionary.
Definition: Dictionary.cpp:276
bool is_empty() const noexcept
We are empty if and only if we are a dictionary with no element.
Definition: Dictionary.h:124
static Dictionary deepcopy(const Dictionary &other)
Create a deep copy of an existing dictionary.
Definition: Dictionary.cpp:43
Dictionary()=default
Default constructor.
void remove(const std::string &key) noexcept
Remove a key-value pair from the dictionary.
Definition: Dictionary.cpp:299
void update(const Dictionary &other)
Update dictionary from another dictionary.
Definition: Dictionary.cpp:411
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:308
size_t serialize(std::vector< char > &buffer) const
Serialize to raw MessagePack data.
Definition: Dictionary.cpp:355
std::vector< std::string > keys() const noexcept
Return the list of keys of the dictionary.
Definition: Dictionary.cpp:246
std::vector< std::pair< std::string, std::reference_wrapper< const Dictionary > > > items() const noexcept
Return an iterable view of the dictionary's (key, value) pairs.
Definition: Dictionary.cpp:256
void write(const std::string &filename) const
Write MessagePack serialization to a binary file.
Definition: Dictionary.cpp:345
void clear() noexcept
Remove all entries from the dictionary.
Definition: Dictionary.cpp:38
Dictionary difference(const Dictionary &other) const
Compute the difference between this dictionary and another.
Definition: Dictionary.cpp:190
std::vector< std::reference_wrapper< const Dictionary > > values() const noexcept
Return an iterable view of the dictionary's values.
Definition: Dictionary.cpp:266
bool is_value() const noexcept
We are a value if and only if the internal value is non-empty.
Definition: Dictionary.h:127
void deserialize(mpack_node_t node)
Update value from an MPack node.
Definition: Value.h:92
void print(std::ostream &stream) const
Print value to an output stream;.
Definition: Value.h:98
void serialize(mpack::Writer &writer) const
Serialize value to a MessagePack writer.
Definition: Value.h:104
const char *(* type_name)()
Function returning the name of the object's type.
Definition: Value.h:171
Requested dictionary key is not found.
Definition: KeyError.h:13
Requested type doesn't match the one already in the dictionary.
Definition: TypeError.h:13
Main library namespace.
Definition: Dictionary.h:34
std::ostream & operator<<(std::ostream &stream, const Dictionary &dict)
Definition: Dictionary.cpp:388