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.
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
) :
73 KeyValueTreeTransformRuleBuilder
addRule() override
75 return KeyValueTreeTransformRuleBuilder(impl_
, prefix_
);
78 KeyValueTreeTransformRulesScoped
scopedTransform(const KeyValueTreePath
& scope
) override
80 return KeyValueTreeTransformRulesScoped(impl_
, prefix_
+ scope
);
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()
110 /********************************************************************
111 * IKeyValueTreeBackMapping
114 IKeyValueTreeBackMapping::~IKeyValueTreeBackMapping() {}
119 class KeyValueTreeBackMapping
: public IKeyValueTreeBackMapping
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
);
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())
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_
; }
187 /********************************************************************
188 * KeyValueTreeTransformerImpl
191 class KeyValueTreeTransformerImpl
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())
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
;
240 result
->push_back(path
);
244 rule
.collectMappedPaths(path
, result
);
249 KeyValueTreePath targetPath_
;
250 std::string targetKey_
;
251 std::type_index expectedType_
;
252 TransformFunction transform_
;
253 ChildRuleMap childRules_
;
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_
));
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_
))
339 applyTransformedValue(rule
, valueBuilder
.build());
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());
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
);
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()));
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
);
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
444 typedef internal::KeyValueTreeTransformerImpl::Rule Rule
;
446 explicit Data(const KeyValueTreePath
& prefix
) :
448 expectedType_(typeid(void)),
449 keyMatchType_(StringCompareType::Exact
),
454 void createRule(internal::KeyValueTreeTransformerImpl
* impl
)
458 createRuleWithKeyMatchType(impl
);
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_
);
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_
;
504 /********************************************************************
505 * KeyValueTreeTransformRuleBuilder
508 KeyValueTreeTransformRuleBuilder::KeyValueTreeTransformRuleBuilder(internal::KeyValueTreeTransformerImpl
* impl
,
509 const KeyValueTreePath
& prefix
) :
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());