Add back-mapping to KeyValueTreeTransform
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 2 Oct 2016 04:58:38 +0000 (07:58 +0300)
committerMark Abraham <mark.j.abraham@gmail.com>
Thu, 13 Oct 2016 09:12:14 +0000 (11:12 +0200)
Add ability to map paths from the output structure of the transform back
to the original structure, which will be useful for error messages.

Change-Id: I9dded5a517fa72177219294596e5c23bec7785ce

src/gromacs/utility/keyvaluetreetransform.cpp
src/gromacs/utility/keyvaluetreetransform.h
src/gromacs/utility/tests/keyvaluetreetransform.cpp
src/gromacs/utility/tests/refdata/TreeValueTransformTest_ObjectFromMultipleStrings.xml
src/gromacs/utility/tests/refdata/TreeValueTransformTest_ObjectFromString.xml
src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransforms.xml
src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransformsCaseAndDashInsensitive.xml
src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransformsToObject.xml

index e30c3314cdc513a423d6eb76ce712818d8910d2d..b3147272a23c51060c39232e56497d2e8f2b3a03 100644 (file)
@@ -56,6 +56,83 @@ IKeyValueTreeTransformRules::~IKeyValueTreeTransformRules()
 {
 }
 
+/********************************************************************
+ * IKeyValueTreeBackMapping
+ */
+
+IKeyValueTreeBackMapping::~IKeyValueTreeBackMapping()
+{
+}
+
+namespace
+{
+
+class KeyValueTreeBackMapping : public IKeyValueTreeBackMapping
+{
+    public:
+        class Entry
+        {
+            public:
+                Entry() = default;
+                explicit Entry(const std::vector<std::string> &path) : sourcePath_(path) {}
+
+                Entry *getOrCreateChildEntry(const std::string &key)
+                {
+                    auto iter = childEntries_.find(key);
+                    if (iter == childEntries_.end())
+                    {
+                        iter = childEntries_.insert(std::make_pair(key, Entry())).first;
+                    }
+                    return &iter->second;
+                }
+                void setMapping(const std::vector<std::string> &path,
+                                const KeyValueTreeValue        &value)
+                {
+                    if (value.isObject())
+                    {
+                        const KeyValueTreeObject &object = value.asObject();
+                        for (const auto &prop : object.properties())
+                        {
+                            childEntries_[prop.key()] = Entry(path);
+                        }
+                    }
+                    else
+                    {
+                        sourcePath_ = path;
+                    }
+                }
+
+                std::vector<std::string>     sourcePath_;
+                std::map<std::string, Entry> childEntries_;
+        };
+
+        virtual std::vector<std::string>
+        originalPath(const std::vector<std::string> &path) const
+        {
+            const Entry *entry = &rootEntry_;
+            for (const auto &element : path)
+            {
+                auto iter = entry->childEntries_.find(element);
+                if (iter == entry->childEntries_.end())
+                {
+                    break;
+                }
+                entry = &iter->second;
+            }
+            GMX_RELEASE_ASSERT(entry->childEntries_.empty()
+                               && !entry->sourcePath_.empty(),
+                               "Requested path not uniquely mapped");
+            return entry->sourcePath_;
+        }
+
+        Entry *rootEntry() { return &rootEntry_; }
+
+    private:
+        Entry rootEntry_;
+};
+
+}   // namespace
+
 namespace internal
 {
 
@@ -78,13 +155,6 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
                 {
                 }
 
-                void doTransform(KeyValueTreeBuilder     *builder,
-                                 const KeyValueTreeValue &value) const;
-                void doChildTransforms(KeyValueTreeBuilder      *builder,
-                                       const KeyValueTreeObject &object) const;
-                void applyTransformedValue(KeyValueTreeBuilder  *builder,
-                                           KeyValueTreeValue   &&value) const;
-
                 const Rule *findMatchingChildRule(const std::string &key) const
                 {
                     auto iter = childRules_.find(key);
@@ -136,6 +206,36 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
                 ChildRuleMap                childRules_;
         };
 
+        class Transformer
+        {
+            public:
+                Transformer(const KeyValueTreeTransformerImpl &impl)
+                    : impl_(impl), backMapping_(new KeyValueTreeBackMapping)
+                {
+                }
+
+                void transform(const KeyValueTreeObject &tree)
+                {
+                    doChildTransforms(impl_.rootRule_.get(), tree);
+                }
+
+                KeyValueTreeTransformResult result()
+                {
+                    return KeyValueTreeTransformResult(builder_.build(),
+                                                       std::move(backMapping_));
+                }
+
+            private:
+                void doTransform(const Rule *rule, const KeyValueTreeValue &value);
+                void doChildTransforms(const Rule *rule, const KeyValueTreeObject &object);
+                void applyTransformedValue(const Rule *rule, KeyValueTreeValue &&value);
+
+                const KeyValueTreeTransformerImpl       &impl_;
+                KeyValueTreeBuilder                      builder_;
+                std::unique_ptr<KeyValueTreeBackMapping> backMapping_;
+                std::vector<std::string>                 context_;
+        };
+
         virtual KeyValueTreeTransformRuleBuilder addRule()
         {
             return KeyValueTreeTransformRuleBuilder(this);
@@ -160,43 +260,46 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
 };
 
 /********************************************************************
- * KeyValueTreeTransformerImpl::Rule
+ * KeyValueTreeTransformerImpl::Transformer
  */
 
-void KeyValueTreeTransformerImpl::Rule::doTransform(
-        KeyValueTreeBuilder *builder, const KeyValueTreeValue &value) const
+void KeyValueTreeTransformerImpl::Transformer::doTransform(
+        const Rule *rule, const KeyValueTreeValue &value)
 {
-    if (transform_)
+    if (rule->transform_)
     {
         KeyValueTreeValueBuilder valueBuilder;
-        transform_(&valueBuilder, value);
-        applyTransformedValue(builder, valueBuilder.build());
+        rule->transform_(&valueBuilder, value);
+        applyTransformedValue(rule, valueBuilder.build());
         return;
     }
-    if (!childRules_.empty())
+    if (!rule->childRules_.empty())
     {
-        doChildTransforms(builder, value.asObject());
+        doChildTransforms(rule, value.asObject());
     }
 }
 
-void KeyValueTreeTransformerImpl::Rule::doChildTransforms(
-        KeyValueTreeBuilder *builder, const KeyValueTreeObject &object) const
+void KeyValueTreeTransformerImpl::Transformer::doChildTransforms(
+        const Rule *rule, const KeyValueTreeObject &object)
 {
     for (const auto &prop : object.properties())
     {
-        const Rule *childRule = findMatchingChildRule(prop.key());
+        const Rule *childRule = rule->findMatchingChildRule(prop.key());
         if (childRule != nullptr)
         {
-            childRule->doTransform(builder, prop.value());
+            context_.push_back(prop.key());
+            doTransform(childRule, prop.value());
+            context_.pop_back();
         }
     }
 }
 
-void KeyValueTreeTransformerImpl::Rule::applyTransformedValue(
-        KeyValueTreeBuilder *builder, KeyValueTreeValue &&value) const
+void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue(
+        const Rule *rule, KeyValueTreeValue &&value)
 {
-    KeyValueTreeObjectBuilder objBuilder = builder->rootObject();
-    for (const std::string &key : targetPath_)
+    KeyValueTreeObjectBuilder       objBuilder = builder_.rootObject();
+    KeyValueTreeBackMapping::Entry *mapEntry   = backMapping_->rootEntry();
+    for (const std::string &key : rule->targetPath_)
     {
         if (objBuilder.keyExists(key))
         {
@@ -206,14 +309,17 @@ void KeyValueTreeTransformerImpl::Rule::applyTransformedValue(
         {
             objBuilder = objBuilder.addObject(key);
         }
+        mapEntry = mapEntry->getOrCreateChildEntry(key);
     }
-    if (objBuilder.keyExists(targetKey_))
+    mapEntry = mapEntry->getOrCreateChildEntry(rule->targetKey_);
+    mapEntry->setMapping(context_, value);
+    if (objBuilder.keyExists(rule->targetKey_))
     {
-        objBuilder.getObject(targetKey_).mergeObject(std::move(value));
+        objBuilder.getObject(rule->targetKey_).mergeObject(std::move(value));
     }
     else
     {
-        objBuilder.addRawValue(targetKey_, std::move(value));
+        objBuilder.addRawValue(rule->targetKey_, std::move(value));
     }
 }
 
@@ -247,11 +353,12 @@ std::vector<std::string> KeyValueTreeTransformer::mappedPaths() const
     return result;
 }
 
-KeyValueTreeObject KeyValueTreeTransformer::transform(const KeyValueTreeObject &tree) const
+KeyValueTreeTransformResult
+KeyValueTreeTransformer::transform(const KeyValueTreeObject &tree) const
 {
-    gmx::KeyValueTreeBuilder builder;
-    impl_->rootRule_->doChildTransforms(&builder, tree);
-    return builder.build();
+    internal::KeyValueTreeTransformerImpl::Transformer transformer(*impl_);
+    transformer.transform(tree);
+    return transformer.result();
 }
 
 /********************************************************************
index 625f8940c4d0b52f57caecc9a3043e6ee19097d4..12b627dbdb3e4b85a03edd7648d95ca921f33c59 100644 (file)
 #include <vector>
 
 #include "gromacs/utility/classhelpers.h"
+#include "gromacs/utility/keyvaluetree.h"
 #include "gromacs/utility/variant.h"
 
 namespace gmx
 {
 
-class KeyValueTreeObject;
 class KeyValueTreeObjectBuilder;
 
 enum class StringCompareType;
@@ -74,6 +74,36 @@ class IKeyValueTreeTransformRules
         ~IKeyValueTreeTransformRules();
 };
 
+class IKeyValueTreeBackMapping
+{
+    public:
+        virtual ~IKeyValueTreeBackMapping();
+
+        virtual std::vector<std::string>
+        originalPath(const std::vector<std::string> &path) const = 0;
+};
+
+class KeyValueTreeTransformResult
+{
+    public:
+        KeyValueTreeObject object() { return std::move(object_); }
+        const IKeyValueTreeBackMapping &backMapping() const { return *mapping_; }
+
+    private:
+        typedef std::unique_ptr<IKeyValueTreeBackMapping> MappingPointer;
+
+        KeyValueTreeTransformResult(KeyValueTreeObject &&object,
+                                    MappingPointer     &&mapping)
+            : object_(std::move(object)), mapping_(std::move(mapping))
+        {
+        }
+
+        KeyValueTreeObject                         object_;
+        std::unique_ptr<IKeyValueTreeBackMapping>  mapping_;
+
+        friend class internal::KeyValueTreeTransformerImpl;
+};
+
 class KeyValueTreeTransformer
 {
     public:
@@ -84,7 +114,7 @@ class KeyValueTreeTransformer
 
         std::vector<std::string> mappedPaths() const;
 
-        KeyValueTreeObject transform(const KeyValueTreeObject &tree) const;
+        KeyValueTreeTransformResult transform(const KeyValueTreeObject &tree) const;
 
     private:
         PrivateImplPointer<internal::KeyValueTreeTransformerImpl> impl_;
index 316e69a703c0778c0ec019235e40ed95f5757d2f..30f444d643a5672f042631296c2f61db7382d227 100644 (file)
@@ -58,14 +58,47 @@ class TreeValueTransformTest : public ::testing::Test
         void testTransform(const gmx::KeyValueTreeObject      &input,
                            const gmx::KeyValueTreeTransformer &transform)
         {
-            gmx::KeyValueTreeObject         result = transform.transform(input);
+            gmx::KeyValueTreeTransformResult  result = transform.transform(input);
+            gmx::KeyValueTreeObject           object = result.object();
 
-            gmx::test::TestReferenceData    data;
-            gmx::test::TestReferenceChecker checker(data.rootChecker());
+            gmx::test::TestReferenceData      data;
+            gmx::test::TestReferenceChecker   checker(data.rootChecker());
             checker.checkKeyValueTreeObject(input, "Input");
             auto mappedPaths = transform.mappedPaths();
             checker.checkSequence(mappedPaths.begin(), mappedPaths.end(), "MappedPaths");
-            checker.checkKeyValueTreeObject(result, "Tree");
+            checker.checkKeyValueTreeObject(object, "Tree");
+            checkBackMapping(&checker, object, result.backMapping());
+        }
+
+    private:
+        void checkBackMapping(gmx::test::TestReferenceChecker     *checker,
+                              const gmx::KeyValueTreeObject       &object,
+                              const gmx::IKeyValueTreeBackMapping &mapping)
+        {
+            auto compound(checker->checkCompound("BackMapping", "Mapping"));
+            checkBackMappingImpl(&compound, object, mapping, std::vector<std::string>());
+        }
+
+        void checkBackMappingImpl(gmx::test::TestReferenceChecker     *checker,
+                                  const gmx::KeyValueTreeObject       &object,
+                                  const gmx::IKeyValueTreeBackMapping &mapping,
+                                  const std::vector<std::string>      &prefix)
+        {
+            for (const auto &prop : object.properties())
+            {
+                std::vector<std::string> path = prefix;
+                path.push_back(prop.key());
+                if (prop.value().isObject())
+                {
+                    checkBackMappingImpl(checker, prop.value().asObject(), mapping, path);
+                }
+                else
+                {
+                    std::string strPath = "/" + gmx::joinStrings(path, "/");
+                    std::string orgPath = "/" + gmx::joinStrings(mapping.originalPath(path), "/");
+                    checker->checkString(orgPath, strPath.c_str());
+                }
+            }
         }
 };
 
index 86708bf77ea5ec1e0d2ffc4e77eb74c2b2b179ae..2815bdbc266f9fadb28d153c893a8a6f165a9811 100644 (file)
@@ -17,4 +17,9 @@
       <Int Name="c">3</Int>
     </Object>
   </Object>
+  <BackMapping Name="Mapping">
+    <String Name="/foo/a">/a</String>
+    <String Name="/foo/b">/b</String>
+    <String Name="/foo/c">/b</String>
+  </BackMapping>
 </ReferenceData>
index dd3353469c813d621108bc95a8494ce7bab27e78..0f0dac97054214fe1c5e6563504b8195e4f5d7b9 100644 (file)
@@ -14,4 +14,8 @@
       <Int Name="b">2</Int>
     </Object>
   </Object>
+  <BackMapping Name="Mapping">
+    <String Name="/foo/a">/a</String>
+    <String Name="/foo/b">/a</String>
+  </BackMapping>
 </ReferenceData>
index 892eefb35741e8f61a4ee3c640cbe58f5c61dae5..1fecf83e2b6ef32da8f8c542be09df81a6bbfcb1 100644 (file)
@@ -14,4 +14,8 @@
     <Int Name="i">1</Int>
     <Int Name="j">2</Int>
   </Object>
+  <BackMapping Name="Mapping">
+    <String Name="/i">/a</String>
+    <String Name="/j">/b</String>
+  </BackMapping>
 </ReferenceData>
index ae12f4ab671636e37cac8bc6d5c00a1af48c64b5..3e4ae5e218cb4f39c66d1d1fd69903bca7d1c29a 100644 (file)
@@ -14,4 +14,8 @@
     <Int Name="i">1</Int>
     <Int Name="j">2</Int>
   </Object>
+  <BackMapping Name="Mapping">
+    <String Name="/i">/a-x</String>
+    <String Name="/j">/by</String>
+  </BackMapping>
 </ReferenceData>
index 7a80978d548233e52e290fdda87c70e61f686dd1..603fd2bf8b41772967dbb9936e0dd388815e6c60 100644 (file)
@@ -16,4 +16,8 @@
       <Int Name="j">2</Int>
     </Object>
   </Object>
+  <BackMapping Name="Mapping">
+    <String Name="/foo/i">/a</String>
+    <String Name="/foo/j">/b</String>
+  </BackMapping>
 </ReferenceData>