Update instructions in containers.rst
[gromacs.git] / src / gromacs / utility / keyvaluetreetransform.cpp
blob45ac7b87237b1abe0317d9a1b407cc74ddc006df
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019,2020, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
35 #include "gmxpre.h"
37 #include "keyvaluetreetransform.h"
39 #include <functional>
40 #include <map>
41 #include <memory>
42 #include <typeindex>
43 #include <vector>
45 #include "gromacs/utility/exceptions.h"
46 #include "gromacs/utility/ikeyvaluetreeerror.h"
47 #include "gromacs/utility/keyvaluetreebuilder.h"
48 #include "gromacs/utility/stringcompare.h"
49 #include "gromacs/utility/stringutil.h"
51 namespace gmx
54 /********************************************************************
55 * IKeyValueTreeTransformRules
58 IKeyValueTreeTransformRules::~IKeyValueTreeTransformRules() {}
60 /********************************************************************
61 * KeyValueTreeTransformRulesScoped::Impl
64 class KeyValueTreeTransformRulesScoped::Impl : public IKeyValueTreeTransformRules
66 public:
67 Impl(internal::KeyValueTreeTransformerImpl* impl, const KeyValueTreePath& prefix) :
68 impl_(impl),
69 prefix_(prefix)
73 KeyValueTreeTransformRuleBuilder addRule() override
75 return KeyValueTreeTransformRuleBuilder(impl_, prefix_);
78 KeyValueTreeTransformRulesScoped scopedTransform(const KeyValueTreePath& scope) override
80 return KeyValueTreeTransformRulesScoped(impl_, prefix_ + scope);
83 private:
84 internal::KeyValueTreeTransformerImpl* impl_;
85 KeyValueTreePath prefix_;
88 /********************************************************************
89 * KeyValueTreeTransformRulesScoped
92 KeyValueTreeTransformRulesScoped::KeyValueTreeTransformRulesScoped(internal::KeyValueTreeTransformerImpl* impl,
93 const KeyValueTreePath& prefix) :
94 impl_(new Impl(impl, prefix))
98 KeyValueTreeTransformRulesScoped::KeyValueTreeTransformRulesScoped(KeyValueTreeTransformRulesScoped&&) noexcept = default;
100 KeyValueTreeTransformRulesScoped& KeyValueTreeTransformRulesScoped::
101 operator=(KeyValueTreeTransformRulesScoped&&) noexcept = default;
103 KeyValueTreeTransformRulesScoped::~KeyValueTreeTransformRulesScoped() {}
105 IKeyValueTreeTransformRules* KeyValueTreeTransformRulesScoped::rules()
107 return impl_.get();
110 /********************************************************************
111 * IKeyValueTreeBackMapping
114 IKeyValueTreeBackMapping::~IKeyValueTreeBackMapping() {}
116 namespace
119 class KeyValueTreeBackMapping : public IKeyValueTreeBackMapping
121 public:
122 class Entry
124 public:
125 Entry() = default;
126 explicit Entry(const KeyValueTreePath& path) : sourcePath_(path) {}
128 Entry* getOrCreateChildEntry(const std::string& key)
130 auto iter = childEntries_.find(key);
131 if (iter == childEntries_.end())
133 iter = childEntries_.insert(std::make_pair(key, Entry())).first;
135 return &iter->second;
137 void setMapping(const KeyValueTreePath& path, const KeyValueTreeValue& value)
139 GMX_RELEASE_ASSERT(sourcePath_.empty(), "Multiple entries map to same path");
140 if (value.isObject())
142 const KeyValueTreeObject& object = value.asObject();
143 for (const auto& prop : object.properties())
145 GMX_RELEASE_ASSERT(!prop.value().isObject(), "Nested objects not implemented");
146 childEntries_[prop.key()] = Entry(path);
149 else
151 sourcePath_ = path;
155 KeyValueTreePath sourcePath_;
156 std::map<std::string, Entry> childEntries_;
159 KeyValueTreePath originalPath(const KeyValueTreePath& path) const override
161 const Entry* entry = &rootEntry_;
162 for (const auto& element : path.elements())
164 auto iter = entry->childEntries_.find(element);
165 if (iter == entry->childEntries_.end())
167 break;
169 entry = &iter->second;
171 GMX_RELEASE_ASSERT(entry->childEntries_.empty() && !entry->sourcePath_.empty(),
172 "Requested path not uniquely mapped");
173 return entry->sourcePath_;
176 Entry* rootEntry() { return &rootEntry_; }
178 private:
179 Entry rootEntry_;
182 } // namespace
184 namespace internal
187 /********************************************************************
188 * KeyValueTreeTransformerImpl
191 class KeyValueTreeTransformerImpl
193 public:
194 class Rule
196 public:
197 typedef std::function<void(KeyValueTreeValueBuilder*, const KeyValueTreeValue&)> TransformFunction;
198 typedef std::map<std::string, Rule, StringCompare> ChildRuleMap;
200 explicit Rule(StringCompareType keyMatchType) :
201 expectedType_(typeid(void)),
202 childRules_(keyMatchType)
206 const Rule* findMatchingChildRule(const std::string& key) const
208 auto iter = childRules_.find(key);
209 if (iter == childRules_.end())
211 return nullptr;
213 return &iter->second;
215 Rule* getOrCreateChildRule(const std::string& key)
217 auto iter = childRules_.find(key);
218 if (iter == childRules_.end())
220 return createChildRule(key, StringCompareType::Exact);
222 return &iter->second;
224 Rule* createChildRule(const std::string& key, StringCompareType keyMatchType)
226 auto result = childRules_.insert(std::make_pair(key, Rule(keyMatchType)));
227 GMX_RELEASE_ASSERT(result.second, "Cannot specify key match type after child rules");
228 return &result.first->second;
231 void collectMappedPaths(const KeyValueTreePath& prefix, std::vector<KeyValueTreePath>* result) const
233 for (const auto& value : childRules_)
235 KeyValueTreePath path = prefix;
236 path.append(value.first);
237 const Rule& rule = value.second;
238 if (rule.transform_)
240 result->push_back(path);
242 else
244 rule.collectMappedPaths(path, result);
249 KeyValueTreePath targetPath_;
250 std::string targetKey_;
251 std::type_index expectedType_;
252 TransformFunction transform_;
253 ChildRuleMap childRules_;
256 class Transformer
258 public:
259 explicit Transformer(IKeyValueTreeErrorHandler* errorHandler) :
260 errorHandler_(errorHandler),
261 backMapping_(new KeyValueTreeBackMapping)
263 if (errorHandler_ == nullptr)
265 errorHandler_ = defaultKeyValueTreeErrorHandler();
269 void transform(const Rule* rootRule, const KeyValueTreeObject& tree)
271 if (rootRule != nullptr)
273 doChildTransforms(rootRule, tree);
277 KeyValueTreeTransformResult result()
279 return KeyValueTreeTransformResult(builder_.build(), std::move(backMapping_));
282 private:
283 void doTransform(const Rule* rule, const KeyValueTreeValue& value);
284 void doChildTransforms(const Rule* rule, const KeyValueTreeObject& object);
285 void applyTransformedValue(const Rule* rule, KeyValueTreeValue&& value);
287 IKeyValueTreeErrorHandler* errorHandler_;
288 KeyValueTreeBuilder builder_;
289 std::unique_ptr<KeyValueTreeBackMapping> backMapping_;
290 KeyValueTreePath context_;
293 KeyValueTreeTransformerImpl() : rootScope_(this, KeyValueTreePath()) {}
295 Rule* getOrCreateRootRule()
297 if (rootRule_ == nullptr)
299 createRootRule(StringCompareType::Exact);
301 return rootRule_.get();
303 void createRootRule(StringCompareType keyMatchType)
305 GMX_RELEASE_ASSERT(rootRule_ == nullptr, "Cannot specify key match type after child rules");
306 rootRule_ = std::make_unique<Rule>(keyMatchType);
309 std::unique_ptr<Rule> rootRule_;
310 KeyValueTreeTransformRulesScoped rootScope_;
313 /********************************************************************
314 * KeyValueTreeTransformerImpl::Transformer
317 void KeyValueTreeTransformerImpl::Transformer::doTransform(const Rule* rule, const KeyValueTreeValue& value)
319 if (rule->transform_ != nullptr)
321 KeyValueTreeValueBuilder valueBuilder;
324 if (value.type() != rule->expectedType_)
326 // TODO: Better error message.
327 GMX_THROW(InvalidInputError("Unexpected type of value"));
329 rule->transform_(&valueBuilder, value);
331 catch (UserInputError& ex)
333 if (!errorHandler_->onError(&ex, context_))
335 throw;
337 return;
339 applyTransformedValue(rule, valueBuilder.build());
340 return;
342 if (!rule->childRules_.empty())
344 doChildTransforms(rule, value.asObject());
348 void KeyValueTreeTransformerImpl::Transformer::doChildTransforms(const Rule* rule,
349 const KeyValueTreeObject& object)
351 for (const auto& prop : object.properties())
353 const Rule* childRule = rule->findMatchingChildRule(prop.key());
354 if (childRule != nullptr)
356 context_.append(prop.key());
357 doTransform(childRule, prop.value());
358 context_.pop_back();
363 void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue(const Rule* rule,
364 KeyValueTreeValue&& value)
366 KeyValueTreeObjectBuilder objBuilder = builder_.rootObject();
367 KeyValueTreeBackMapping::Entry* mapEntry = backMapping_->rootEntry();
368 for (const std::string& key : rule->targetPath_.elements())
370 if (objBuilder.keyExists(key))
372 GMX_RELEASE_ASSERT(objBuilder[key].isObject(),
373 "Inconsistent transform (different items map to same path)");
374 objBuilder = objBuilder.getObjectBuilder(key);
376 else
378 objBuilder = objBuilder.addObject(key);
380 mapEntry = mapEntry->getOrCreateChildEntry(key);
382 mapEntry = mapEntry->getOrCreateChildEntry(rule->targetKey_);
383 mapEntry->setMapping(context_, value);
384 if (objBuilder.keyExists(rule->targetKey_))
386 GMX_RELEASE_ASSERT(value.isObject(),
387 "Inconsistent transform (different items map to same path)");
388 GMX_RELEASE_ASSERT(objBuilder[rule->targetKey_].isObject(),
389 "Inconsistent transform (different items map to same path)");
390 objBuilder = objBuilder.getObjectBuilder(rule->targetKey_);
391 GMX_RELEASE_ASSERT(objBuilder.objectHasDistinctProperties(value.asObject()),
392 "Inconsistent transform (different items map to same path)");
393 objBuilder.mergeObject(std::move(value.asObject()));
395 else
397 objBuilder.addRawValue(rule->targetKey_, std::move(value));
401 } // namespace internal
403 /********************************************************************
404 * KeyValueTreeTransformer
407 KeyValueTreeTransformer::KeyValueTreeTransformer() :
408 impl_(new internal::KeyValueTreeTransformerImpl)
412 KeyValueTreeTransformer::~KeyValueTreeTransformer() {}
414 IKeyValueTreeTransformRules* KeyValueTreeTransformer::rules()
416 return impl_->rootScope_.rules();
419 std::vector<KeyValueTreePath> KeyValueTreeTransformer::mappedPaths() const
421 std::vector<KeyValueTreePath> result;
422 if (impl_->rootRule_)
424 impl_->rootRule_->collectMappedPaths(KeyValueTreePath(), &result);
426 return result;
429 KeyValueTreeTransformResult KeyValueTreeTransformer::transform(const KeyValueTreeObject& tree,
430 IKeyValueTreeErrorHandler* errorHandler) const
432 internal::KeyValueTreeTransformerImpl::Transformer transformer(errorHandler);
433 transformer.transform(impl_->rootRule_.get(), tree);
434 return transformer.result();
437 /********************************************************************
438 * KeyValueTreeTransformRuleBuilder::Data
441 class KeyValueTreeTransformRuleBuilder::Data
443 public:
444 typedef internal::KeyValueTreeTransformerImpl::Rule Rule;
446 explicit Data(const KeyValueTreePath& prefix) :
447 prefixPath_(prefix),
448 expectedType_(typeid(void)),
449 keyMatchType_(StringCompareType::Exact),
450 keyMatchRule_(false)
454 void createRule(internal::KeyValueTreeTransformerImpl* impl)
456 if (keyMatchRule_)
458 createRuleWithKeyMatchType(impl);
459 return;
461 GMX_RELEASE_ASSERT(transform_ != nullptr, "Transform has not been specified");
462 Rule* rule = impl->getOrCreateRootRule();
463 for (const std::string& key : fromPath_.elements())
465 GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
466 "Cannot specify multiple rules from a single path");
467 rule = rule->getOrCreateChildRule(key);
469 GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
470 "Cannot specify multiple rules from a single path");
471 rule->targetKey_ = toPath_.pop_last();
472 rule->targetPath_ = std::move(toPath_);
473 rule->expectedType_ = expectedType_;
474 rule->transform_ = transform_;
477 void createRuleWithKeyMatchType(internal::KeyValueTreeTransformerImpl* impl)
479 if (fromPath_.empty())
481 impl->createRootRule(keyMatchType_);
483 else
485 std::string lastKey = fromPath_.pop_last();
486 Rule* rule = impl->getOrCreateRootRule();
487 for (const std::string& key : fromPath_.elements())
489 rule = rule->getOrCreateChildRule(key);
491 rule->createChildRule(lastKey, keyMatchType_);
495 const KeyValueTreePath prefixPath_;
496 KeyValueTreePath fromPath_;
497 KeyValueTreePath toPath_;
498 std::type_index expectedType_;
499 Rule::TransformFunction transform_;
500 StringCompareType keyMatchType_;
501 bool keyMatchRule_;
504 /********************************************************************
505 * KeyValueTreeTransformRuleBuilder
508 KeyValueTreeTransformRuleBuilder::KeyValueTreeTransformRuleBuilder(internal::KeyValueTreeTransformerImpl* impl,
509 const KeyValueTreePath& prefix) :
510 impl_(impl),
511 data_(new Data(prefix))
515 // TODO If the function called here would ever throw
516 // (e.g. std::bad_alloc) then std::terminate will be called.
517 // Alternatively, createRule could catch all exceptions and terminate
518 // but that's the same for an end user. So suppressing the clang-tidy
519 // warning is about as bad as any alternative.
520 KeyValueTreeTransformRuleBuilder::~KeyValueTreeTransformRuleBuilder() // NOLINT(bugprone-exception-escape)
522 if (!std::uncaught_exceptions())
524 data_->createRule(impl_);
528 void KeyValueTreeTransformRuleBuilder::setFromPath(const KeyValueTreePath& path)
530 data_->fromPath_ = path;
533 void KeyValueTreeTransformRuleBuilder::setExpectedType(const std::type_index& type)
535 data_->expectedType_ = type;
538 void KeyValueTreeTransformRuleBuilder::setToPath(const KeyValueTreePath& path)
540 data_->toPath_ = data_->prefixPath_ + path;
543 void KeyValueTreeTransformRuleBuilder::setKeyMatchType(StringCompareType keyMatchType)
545 data_->keyMatchType_ = keyMatchType;
546 data_->keyMatchRule_ = true;
549 void KeyValueTreeTransformRuleBuilder::addTransformToAny(const std::function<Any(const Any&)>& transform)
551 data_->transform_ = [transform](KeyValueTreeValueBuilder* builder, const KeyValueTreeValue& value) {
552 builder->setAnyValue(transform(value.asAny()));
556 void KeyValueTreeTransformRuleBuilder::addTransformToObject(
557 const std::function<void(KeyValueTreeObjectBuilder*, const Any&)>& transform)
559 data_->transform_ = [transform](KeyValueTreeValueBuilder* builder, const KeyValueTreeValue& value) {
560 KeyValueTreeObjectBuilder obj = builder->createObject();
561 transform(&obj, value.asAny());
565 } // namespace gmx