Support different key matching in KeyValueTreeTransform
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 1 Oct 2016 04:53:34 +0000 (07:53 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Wed, 5 Oct 2016 18:26:28 +0000 (20:26 +0200)
Support case-insensitive and mdp-style dash-insensitive string
matching in KeyValueTreeTransform.  Required to use this for mdp
transformations.

Change-Id: I20661f721880e26844f0c38a7582c26f376e6a62

src/gromacs/utility/keyvaluetreetransform.cpp
src/gromacs/utility/keyvaluetreetransform.h
src/gromacs/utility/stringcompare.h [new file with mode: 0644]
src/gromacs/utility/tests/keyvaluetreetransform.cpp
src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransformsCaseAndDashInsensitive.xml [new file with mode: 0644]

index 00eb73c7d7c8c850d2938ebf1246058cb5373538..6ac2975e7260afd751d8828ca9c1af84d1d84b8f 100644 (file)
 
 #include "keyvaluetreetransform.h"
 
-#include <exception>
+#include <functional>
+#include <map>
+#include <memory>
 #include <vector>
 
 #include "gromacs/utility/keyvaluetreebuilder.h"
+#include "gromacs/utility/stringcompare.h"
 #include "gromacs/utility/stringutil.h"
 
 namespace gmx
@@ -53,23 +56,11 @@ IKeyValueTreeTransformRules::~IKeyValueTreeTransformRules()
 {
 }
 
-/********************************************************************
- * KeyValueTreeTransformRule
- */
-
 namespace internal
 {
 
-class KeyValueTreeTransformRule
-{
-    public:
-
-    private:
-        std::function<KeyValueTreeValue(const KeyValueTreeValue &)> transform_;
-};
-
 /********************************************************************
- * KeyValueTreeTransformer::Impl
+ * KeyValueTreeTransformerImpl
  */
 
 class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
@@ -80,6 +71,13 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
             public:
                 typedef std::function<void(KeyValueTreeValueBuilder *, const KeyValueTreeValue &)>
                     TransformFunction;
+                typedef std::map<std::string, Rule, StringCompare> ChildRuleMap;
+
+                explicit Rule(StringCompareType keyMatchType)
+                    : childRules_(keyMatchType)
+                {
+                }
+
                 void doTransform(KeyValueTreeBuilder     *builder,
                                  const KeyValueTreeValue &value) const;
                 void doChildTransforms(KeyValueTreeBuilder      *builder,
@@ -101,15 +99,23 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
                     auto iter = childRules_.find(key);
                     if (iter == childRules_.end())
                     {
-                        iter = childRules_.insert(std::make_pair(key, Rule())).first;
+                        return createChildRule(key, StringCompareType::Exact);
                     }
                     return &iter->second;
                 }
+                Rule *createChildRule(const std::string &key,
+                                      StringCompareType  keyMatchType)
+                {
+                    auto result = childRules_.insert(std::make_pair(key, Rule(keyMatchType)));
+                    GMX_RELEASE_ASSERT(result.second,
+                                       "Cannot specify key match type after child rules");
+                    return &result.first->second;
+                }
 
                 std::vector<std::string>    targetPath_;
                 std::string                 targetKey_;
                 TransformFunction           transform_;
-                std::map<std::string, Rule> childRules_;
+                ChildRuleMap                childRules_;
         };
 
         virtual KeyValueTreeTransformRuleBuilder addRule()
@@ -117,9 +123,28 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
             return KeyValueTreeTransformRuleBuilder(this);
         }
 
-        Rule  rootRule_;
+        Rule *getOrCreateRootRule()
+        {
+            if (rootRule_ == nullptr)
+            {
+                createRootRule(StringCompareType::Exact);
+            }
+            return rootRule_.get();
+        }
+        void createRootRule(StringCompareType keyMatchType)
+        {
+            GMX_RELEASE_ASSERT(rootRule_ == nullptr,
+                               "Cannot specify key match type after child rules");
+            rootRule_.reset(new Rule(keyMatchType));
+        }
+
+        std::unique_ptr<Rule>  rootRule_;
 };
 
+/********************************************************************
+ * KeyValueTreeTransformerImpl::Rule
+ */
+
 void KeyValueTreeTransformerImpl::Rule::doTransform(
         KeyValueTreeBuilder *builder, const KeyValueTreeValue &value) const
 {
@@ -197,7 +222,7 @@ IKeyValueTreeTransformRules *KeyValueTreeTransformer::rules()
 KeyValueTreeObject KeyValueTreeTransformer::transform(const KeyValueTreeObject &tree) const
 {
     gmx::KeyValueTreeBuilder builder;
-    impl_->rootRule_.doChildTransforms(&builder, tree);
+    impl_->rootRule_->doChildTransforms(&builder, tree);
     return builder.build();
 }
 
@@ -208,14 +233,20 @@ KeyValueTreeObject KeyValueTreeTransformer::transform(const KeyValueTreeObject &
 class KeyValueTreeTransformRuleBuilder::Data
 {
     public:
-        typedef internal::KeyValueTreeTransformerImpl::Rule::TransformFunction
-            TransformFunction;
+        typedef internal::KeyValueTreeTransformerImpl::Rule Rule;
+
+        Data() : keyMatchType_(StringCompareType::Exact) {}
 
         void createRule(internal::KeyValueTreeTransformerImpl *impl)
         {
-            std::vector<std::string>                     from = splitDelimitedString(fromPath_.substr(1), '/');
-            std::vector<std::string>                     to   = splitDelimitedString(toPath_.substr(1), '/');
-            internal::KeyValueTreeTransformerImpl::Rule *rule = &impl->rootRule_;
+            if (toPath_.empty())
+            {
+                createRuleWithKeyMatchType(impl);
+                return;
+            }
+            std::vector<std::string>  from = splitDelimitedString(fromPath_.substr(1), '/');
+            std::vector<std::string>  to   = splitDelimitedString(toPath_.substr(1), '/');
+            Rule                     *rule = impl->getOrCreateRootRule();
             for (const std::string &key : from)
             {
                 rule = rule->getOrCreateChildRule(key);
@@ -226,9 +257,30 @@ class KeyValueTreeTransformRuleBuilder::Data
             rule->transform_  = transform_;
         }
 
-        std::string       fromPath_;
-        std::string       toPath_;
-        TransformFunction transform_;
+        void createRuleWithKeyMatchType(internal::KeyValueTreeTransformerImpl *impl)
+        {
+            std::vector<std::string> from = splitDelimitedString(fromPath_.substr(1), '/');
+            if (from.empty())
+            {
+                impl->createRootRule(keyMatchType_);
+            }
+            else
+            {
+                std::string lastKey = from.back();
+                from.pop_back();
+                Rule       *rule = impl->getOrCreateRootRule();
+                for (const std::string &key : from)
+                {
+                    rule = rule->getOrCreateChildRule(key);
+                }
+                rule->createChildRule(lastKey, keyMatchType_);
+            }
+        }
+
+        std::string              fromPath_;
+        std::string              toPath_;
+        Rule::TransformFunction  transform_;
+        StringCompareType        keyMatchType_;
 };
 
 /********************************************************************
@@ -259,6 +311,11 @@ void KeyValueTreeTransformRuleBuilder::setToPath(const std::string &path)
     data_->toPath_ = path;
 }
 
+void KeyValueTreeTransformRuleBuilder::setKeyMatchType(StringCompareType keyMatchType)
+{
+    data_->keyMatchType_ = keyMatchType;
+}
+
 void KeyValueTreeTransformRuleBuilder::addTransformToVariant(
         std::function<Variant(const Variant &)> transform)
 {
index 102df2bd88f869d1154bec1681db213625788b4b..90fd653cfdee2516d92a52a155262a9186d2b6bd 100644 (file)
@@ -55,6 +55,8 @@ namespace gmx
 class KeyValueTreeObject;
 class KeyValueTreeObjectBuilder;
 
+enum class StringCompareType;
+
 class KeyValueTreeTransformRuleBuilder;
 
 namespace internal
@@ -171,10 +173,16 @@ class KeyValueTreeTransformRuleBuilder
             setFromPath(path);
             return AfterFrom<FromType>(this);
         }
+        void keyMatchType(const std::string &path, StringCompareType keyMatchType)
+        {
+            setFromPath(path);
+            setKeyMatchType(keyMatchType);
+        }
 
     private:
         void setFromPath(const std::string &path);
         void setToPath(const std::string &path);
+        void setKeyMatchType(StringCompareType keyMatchType);
         void addTransformToVariant(std::function<Variant(const Variant &)> transform);
         void addTransformToObject(std::function<void(KeyValueTreeObjectBuilder *, const Variant &)> transform);
 
diff --git a/src/gromacs/utility/stringcompare.h b/src/gromacs/utility/stringcompare.h
new file mode 100644 (file)
index 0000000..211a4b6
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2016, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares utility functionst for string comparison.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_STRINGCOMPARE_H
+#define GMX_UTILITY_STRINGCOMPARE_H
+
+#include <string>
+
+#include "gromacs/utility/cstringutil.h"
+
+namespace gmx
+{
+
+//! \cond libapi
+/*! \brief
+ * Specifies how strings should be compared in various contexts.
+ *
+ * \ingroup module_utility
+ */
+enum class StringCompareType
+{
+    //! Only exact matches are accepted.
+    Exact,
+    //! Case-insensitive comparison.
+    CaseInsensitive,
+    //! Case-insensitive comparison that also ignores '-' and '_'.
+    CaseAndDashInsensitive
+};
+//! \endcond
+
+/*! \libinternal \brief
+ * Compare object for std::string STL containers and algorithms that supports
+ * run-time decision on how to compare.
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+class StringCompare
+{
+    public:
+        /*! \brief
+         * Creates a comparer with the given type
+         *
+         * This is not explicit, which allows passing \ref StringCompareType
+         * directly to, e.g., `std::map` constructors.
+         */
+        StringCompare(StringCompareType type = StringCompareType::Exact)
+            : type_(type) {}
+
+        //! The comparison operation.
+        bool operator()(const std::string &a, const std::string &b) const
+        {
+            switch (type_)
+            {
+                case StringCompareType::Exact:
+                    return a < b;
+                case StringCompareType::CaseInsensitive:
+                    return gmx_strcasecmp(a.c_str(), b.c_str()) < 0;
+                case StringCompareType::CaseAndDashInsensitive:
+                    return gmx_strcasecmp_min(a.c_str(), b.c_str()) < 0;
+            }
+            return a < b;
+        }
+
+    private:
+        StringCompareType type_;
+};
+
+} // namespace gmx
+
+#endif
index 11834d830f88ad2294653e1946a3d3b12b1b485c..2c1071abd0b74dace0bc9bff39991a61f1ca8dda 100644 (file)
@@ -44,6 +44,7 @@
 #include "gromacs/utility/keyvaluetree.h"
 #include "gromacs/utility/keyvaluetreebuilder.h"
 #include "gromacs/utility/strconvert.h"
+#include "gromacs/utility/stringcompare.h"
 #include "gromacs/utility/stringutil.h"
 
 #include "testutils/refdata.h"
@@ -82,6 +83,24 @@ TEST_F(TreeValueTransformTest, SimpleTransforms)
     testTransform(input, transform);
 }
 
+TEST_F(TreeValueTransformTest, SimpleTransformsCaseAndDashInsensitive)
+{
+    gmx::KeyValueTreeBuilder     builder;
+    builder.rootObject().addValue<std::string>("a-x", "1");
+    builder.rootObject().addValue<std::string>("by", "2");
+    gmx::KeyValueTreeObject      input = builder.build();
+
+    gmx::KeyValueTreeTransformer transform;
+    transform.rules()->addRule()
+        .keyMatchType("/", gmx::StringCompareType::CaseAndDashInsensitive);
+    transform.rules()->addRule()
+        .from<std::string>("/Ax").to<int>("/i").transformWith(&gmx::fromStdString<int>);
+    transform.rules()->addRule()
+        .from<std::string>("/B-Y").to<int>("/j").transformWith(&gmx::fromStdString<int>);
+
+    testTransform(input, transform);
+}
+
 TEST_F(TreeValueTransformTest, SimpleTransformsToObject)
 {
     gmx::KeyValueTreeBuilder     builder;
diff --git a/src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransformsCaseAndDashInsensitive.xml b/src/gromacs/utility/tests/refdata/TreeValueTransformTest_SimpleTransformsCaseAndDashInsensitive.xml
new file mode 100644 (file)
index 0000000..d7c8a14
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <Object Name="Input">
+    <String Name="a-x">1</String>
+    <String Name="by">2</String>
+  </Object>
+  <Object Name="Tree">
+    <Int Name="i">1</Int>
+    <Int Name="j">2</Int>
+  </Object>
+</ReferenceData>