2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019,2020,2021, 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.
37 #include "keyvaluetreetransform.h"
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"
54 /********************************************************************
55 * IKeyValueTreeTransformRules
58 IKeyValueTreeTransformRules::~IKeyValueTreeTransformRules() {}
60 /********************************************************************
61 * KeyValueTreeTransformRulesScoped::Impl
64 class KeyValueTreeTransformRulesScoped::Impl : public IKeyValueTreeTransformRules
67 Impl(internal::KeyValueTreeTransformerImpl* impl, const KeyValueTreePath& prefix) :
68 impl_(impl), prefix_(prefix)
72 KeyValueTreeTransformRuleBuilder addRule() override
74 return KeyValueTreeTransformRuleBuilder(impl_, prefix_);
77 KeyValueTreeTransformRulesScoped scopedTransform(const KeyValueTreePath& scope) override
79 return KeyValueTreeTransformRulesScoped(impl_, prefix_ + scope);
83 internal::KeyValueTreeTransformerImpl* impl_;
84 KeyValueTreePath prefix_;
87 /********************************************************************
88 * KeyValueTreeTransformRulesScoped
91 KeyValueTreeTransformRulesScoped::KeyValueTreeTransformRulesScoped(internal::KeyValueTreeTransformerImpl* impl,
92 const KeyValueTreePath& prefix) :
93 impl_(new Impl(impl, prefix))
97 KeyValueTreeTransformRulesScoped::KeyValueTreeTransformRulesScoped(KeyValueTreeTransformRulesScoped&&) noexcept = default;
99 KeyValueTreeTransformRulesScoped&
100 KeyValueTreeTransformRulesScoped::operator=(KeyValueTreeTransformRulesScoped&&) noexcept = default;
102 KeyValueTreeTransformRulesScoped::~KeyValueTreeTransformRulesScoped() {}
104 IKeyValueTreeTransformRules* KeyValueTreeTransformRulesScoped::rules()
109 /********************************************************************
110 * IKeyValueTreeBackMapping
113 IKeyValueTreeBackMapping::~IKeyValueTreeBackMapping() {}
118 class KeyValueTreeBackMapping : public IKeyValueTreeBackMapping
125 explicit Entry(const KeyValueTreePath& path) : sourcePath_(path) {}
127 Entry* getOrCreateChildEntry(const std::string& key)
129 auto iter = childEntries_.find(key);
130 if (iter == childEntries_.end())
132 iter = childEntries_.insert(std::make_pair(key, Entry())).first;
134 return &iter->second;
136 void setMapping(const KeyValueTreePath& path, const KeyValueTreeValue& value)
138 GMX_RELEASE_ASSERT(sourcePath_.empty(), "Multiple entries map to same path");
139 if (value.isObject())
141 const KeyValueTreeObject& object = value.asObject();
142 for (const auto& prop : object.properties())
144 GMX_RELEASE_ASSERT(!prop.value().isObject(), "Nested objects not implemented");
145 childEntries_[prop.key()] = Entry(path);
154 KeyValueTreePath sourcePath_;
155 std::map<std::string, Entry> childEntries_;
158 KeyValueTreePath originalPath(const KeyValueTreePath& path) const override
160 const Entry* entry = &rootEntry_;
161 for (const auto& element : path.elements())
163 auto iter = entry->childEntries_.find(element);
164 if (iter == entry->childEntries_.end())
168 entry = &iter->second;
170 GMX_RELEASE_ASSERT(entry->childEntries_.empty() && !entry->sourcePath_.empty(),
171 "Requested path not uniquely mapped");
172 return entry->sourcePath_;
175 Entry* rootEntry() { return &rootEntry_; }
186 /********************************************************************
187 * KeyValueTreeTransformerImpl
190 class KeyValueTreeTransformerImpl
196 typedef std::function<void(KeyValueTreeValueBuilder*, const KeyValueTreeValue&)> TransformFunction;
197 typedef std::map<std::string, Rule, StringCompare> ChildRuleMap;
199 explicit Rule(StringCompareType keyMatchType) :
200 expectedType_(typeid(void)), childRules_(keyMatchType)
204 const Rule* findMatchingChildRule(const std::string& key) const
206 auto iter = childRules_.find(key);
207 if (iter == childRules_.end())
211 return &iter->second;
213 Rule* getOrCreateChildRule(const std::string& key)
215 auto iter = childRules_.find(key);
216 if (iter == childRules_.end())
218 return createChildRule(key, StringCompareType::Exact);
220 return &iter->second;
222 Rule* createChildRule(const std::string& key, StringCompareType keyMatchType)
224 auto result = childRules_.insert(std::make_pair(key, Rule(keyMatchType)));
225 GMX_RELEASE_ASSERT(result.second, "Cannot specify key match type after child rules");
226 return &result.first->second;
229 void collectMappedPaths(const KeyValueTreePath& prefix, std::vector<KeyValueTreePath>* result) const
231 for (const auto& value : childRules_)
233 KeyValueTreePath path = prefix;
234 path.append(value.first);
235 const Rule& rule = value.second;
238 result->push_back(path);
242 rule.collectMappedPaths(path, result);
247 KeyValueTreePath targetPath_;
248 std::string targetKey_;
249 std::type_index expectedType_;
250 TransformFunction transform_;
251 ChildRuleMap childRules_;
257 explicit Transformer(IKeyValueTreeErrorHandler* errorHandler) :
258 errorHandler_(errorHandler), backMapping_(new KeyValueTreeBackMapping)
260 if (errorHandler_ == nullptr)
262 errorHandler_ = defaultKeyValueTreeErrorHandler();
266 void transform(const Rule* rootRule, const KeyValueTreeObject& tree)
268 if (rootRule != nullptr)
270 doChildTransforms(rootRule, tree);
274 KeyValueTreeTransformResult result()
276 return KeyValueTreeTransformResult(builder_.build(), std::move(backMapping_));
280 void doTransform(const Rule* rule, const KeyValueTreeValue& value);
281 void doChildTransforms(const Rule* rule, const KeyValueTreeObject& object);
282 void applyTransformedValue(const Rule* rule, KeyValueTreeValue&& value);
284 IKeyValueTreeErrorHandler* errorHandler_;
285 KeyValueTreeBuilder builder_;
286 std::unique_ptr<KeyValueTreeBackMapping> backMapping_;
287 KeyValueTreePath context_;
290 KeyValueTreeTransformerImpl() : rootScope_(this, KeyValueTreePath()) {}
292 Rule* getOrCreateRootRule()
294 if (rootRule_ == nullptr)
296 createRootRule(StringCompareType::Exact);
298 return rootRule_.get();
300 void createRootRule(StringCompareType keyMatchType)
302 GMX_RELEASE_ASSERT(rootRule_ == nullptr, "Cannot specify key match type after child rules");
303 rootRule_ = std::make_unique<Rule>(keyMatchType);
306 std::unique_ptr<Rule> rootRule_;
307 KeyValueTreeTransformRulesScoped rootScope_;
310 /********************************************************************
311 * KeyValueTreeTransformerImpl::Transformer
314 void KeyValueTreeTransformerImpl::Transformer::doTransform(const Rule* rule, const KeyValueTreeValue& value)
316 if (rule->transform_ != nullptr)
318 KeyValueTreeValueBuilder valueBuilder;
321 if (value.type() != rule->expectedType_)
323 // TODO: Better error message.
324 GMX_THROW(InvalidInputError("Unexpected type of value"));
326 rule->transform_(&valueBuilder, value);
328 catch (UserInputError& ex)
330 if (!errorHandler_->onError(&ex, context_))
336 applyTransformedValue(rule, valueBuilder.build());
339 if (!rule->childRules_.empty())
341 doChildTransforms(rule, value.asObject());
345 void KeyValueTreeTransformerImpl::Transformer::doChildTransforms(const Rule* rule,
346 const KeyValueTreeObject& object)
348 for (const auto& prop : object.properties())
350 const Rule* childRule = rule->findMatchingChildRule(prop.key());
351 if (childRule != nullptr)
353 context_.append(prop.key());
354 doTransform(childRule, prop.value());
360 void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue(const Rule* rule,
361 KeyValueTreeValue&& value)
363 KeyValueTreeObjectBuilder objBuilder = builder_.rootObject();
364 KeyValueTreeBackMapping::Entry* mapEntry = backMapping_->rootEntry();
365 for (const std::string& key : rule->targetPath_.elements())
367 if (objBuilder.keyExists(key))
369 GMX_RELEASE_ASSERT(objBuilder[key].isObject(),
370 "Inconsistent transform (different items map to same path)");
371 objBuilder = objBuilder.getObjectBuilder(key);
375 objBuilder = objBuilder.addObject(key);
377 mapEntry = mapEntry->getOrCreateChildEntry(key);
379 mapEntry = mapEntry->getOrCreateChildEntry(rule->targetKey_);
380 mapEntry->setMapping(context_, value);
381 if (objBuilder.keyExists(rule->targetKey_))
383 GMX_RELEASE_ASSERT(value.isObject(),
384 "Inconsistent transform (different items map to same path)");
385 GMX_RELEASE_ASSERT(objBuilder[rule->targetKey_].isObject(),
386 "Inconsistent transform (different items map to same path)");
387 objBuilder = objBuilder.getObjectBuilder(rule->targetKey_);
388 GMX_RELEASE_ASSERT(objBuilder.objectHasDistinctProperties(value.asObject()),
389 "Inconsistent transform (different items map to same path)");
390 objBuilder.mergeObject(std::move(value.asObject()));
394 objBuilder.addRawValue(rule->targetKey_, std::move(value));
398 } // namespace internal
400 /********************************************************************
401 * KeyValueTreeTransformer
404 KeyValueTreeTransformer::KeyValueTreeTransformer() :
405 impl_(new internal::KeyValueTreeTransformerImpl)
409 KeyValueTreeTransformer::~KeyValueTreeTransformer() {}
411 IKeyValueTreeTransformRules* KeyValueTreeTransformer::rules()
413 return impl_->rootScope_.rules();
416 std::vector<KeyValueTreePath> KeyValueTreeTransformer::mappedPaths() const
418 std::vector<KeyValueTreePath> result;
419 if (impl_->rootRule_)
421 impl_->rootRule_->collectMappedPaths(KeyValueTreePath(), &result);
426 KeyValueTreeTransformResult KeyValueTreeTransformer::transform(const KeyValueTreeObject& tree,
427 IKeyValueTreeErrorHandler* errorHandler) const
429 internal::KeyValueTreeTransformerImpl::Transformer transformer(errorHandler);
430 transformer.transform(impl_->rootRule_.get(), tree);
431 return transformer.result();
434 /********************************************************************
435 * KeyValueTreeTransformRuleBuilder::Data
438 class KeyValueTreeTransformRuleBuilder::Data
441 typedef internal::KeyValueTreeTransformerImpl::Rule Rule;
443 explicit Data(const KeyValueTreePath& prefix) :
445 expectedType_(typeid(void)),
446 keyMatchType_(StringCompareType::Exact),
451 void createRule(internal::KeyValueTreeTransformerImpl* impl)
455 createRuleWithKeyMatchType(impl);
458 GMX_RELEASE_ASSERT(transform_ != nullptr, "Transform has not been specified");
459 Rule* rule = impl->getOrCreateRootRule();
460 for (const std::string& key : fromPath_.elements())
462 GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
463 "Cannot specify multiple rules from a single path");
464 rule = rule->getOrCreateChildRule(key);
466 GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
467 "Cannot specify multiple rules from a single path");
468 rule->targetKey_ = toPath_.pop_last();
469 rule->targetPath_ = std::move(toPath_);
470 rule->expectedType_ = expectedType_;
471 rule->transform_ = transform_;
474 void createRuleWithKeyMatchType(internal::KeyValueTreeTransformerImpl* impl)
476 if (fromPath_.empty())
478 impl->createRootRule(keyMatchType_);
482 std::string lastKey = fromPath_.pop_last();
483 Rule* rule = impl->getOrCreateRootRule();
484 for (const std::string& key : fromPath_.elements())
486 rule = rule->getOrCreateChildRule(key);
488 rule->createChildRule(lastKey, keyMatchType_);
492 const KeyValueTreePath prefixPath_;
493 KeyValueTreePath fromPath_;
494 KeyValueTreePath toPath_;
495 std::type_index expectedType_;
496 Rule::TransformFunction transform_;
497 StringCompareType keyMatchType_;
501 /********************************************************************
502 * KeyValueTreeTransformRuleBuilder
505 KeyValueTreeTransformRuleBuilder::KeyValueTreeTransformRuleBuilder(internal::KeyValueTreeTransformerImpl* impl,
506 const KeyValueTreePath& prefix) :
507 impl_(impl), data_(new Data(prefix))
511 // TODO If the function called here would ever throw
512 // (e.g. std::bad_alloc) then std::terminate will be called.
513 // Alternatively, createRule could catch all exceptions and terminate
514 // but that's the same for an end user. So suppressing the clang-tidy
515 // warning is about as bad as any alternative.
516 KeyValueTreeTransformRuleBuilder::~KeyValueTreeTransformRuleBuilder() // NOLINT(bugprone-exception-escape)
518 if (!std::uncaught_exceptions())
520 data_->createRule(impl_);
524 void KeyValueTreeTransformRuleBuilder::setFromPath(const KeyValueTreePath& path)
526 data_->fromPath_ = path;
529 void KeyValueTreeTransformRuleBuilder::setExpectedType(const std::type_index& type)
531 data_->expectedType_ = type;
534 void KeyValueTreeTransformRuleBuilder::setToPath(const KeyValueTreePath& path)
536 data_->toPath_ = data_->prefixPath_ + path;
539 void KeyValueTreeTransformRuleBuilder::setKeyMatchType(StringCompareType keyMatchType)
541 data_->keyMatchType_ = keyMatchType;
542 data_->keyMatchRule_ = true;
545 void KeyValueTreeTransformRuleBuilder::addTransformToAny(const std::function<Any(const Any&)>& transform)
547 data_->transform_ = [transform](KeyValueTreeValueBuilder* builder, const KeyValueTreeValue& value) {
548 builder->setAnyValue(transform(value.asAny()));
552 void KeyValueTreeTransformRuleBuilder::addTransformToObject(
553 const std::function<void(KeyValueTreeObjectBuilder*, const Any&)>& transform)
555 data_->transform_ = [transform](KeyValueTreeValueBuilder* builder, const KeyValueTreeValue& value) {
556 KeyValueTreeObjectBuilder obj = builder->createObject();
557 transform(&obj, value.asAny());