class KeyValueTreeObject;
class TextWriter;
+/*! \libinternal \brief
+ * Identifies an entry in a key-value tree.
+ *
+ * This class is mainly an internal utility within the key-value tree
+ * implementation, but it is exposed on the API level where string-based
+ * specification of a location in the tree is necessary. Constructors are not
+ * explicit to allow passing a simple string in contexts where a
+ * KeyValueTreePath is expected.
+ *
+ * The string specifying a location should start with a `/`, followed by the
+ * names of the properties separated by `/`. For example, `/a/b/c` specifies
+ * property `c` in an object that is the value of `b` in an object that is the
+ * value of `a` at the root of the tree.
+ * Currently, there is no support for specifying paths to values within arrays
+ * (since none of the places where this is used implement array handling,
+ * either).
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
class KeyValueTreePath
{
public:
+ //! Creates an empty path (corresponds to the root object).
KeyValueTreePath() = default;
+ //! Creates a path from given string representation.
+ KeyValueTreePath(const char *path);
+ //! Creates a path from given string representation.
KeyValueTreePath(const std::string &path);
+ //! Adds another element to the path, making it a child of the old path.
void append(const std::string &key) { path_.push_back(key); }
+ //! Removes the last element in the path, making it the parent path.
void pop_back() { return path_.pop_back(); }
+ //! Removes and returns the last element in the path.
std::string pop_last()
{
std::string result = std::move(path_.back());
return result;
}
+ //! Whether the path is empty (pointing to the root object).
bool empty() const { return path_.empty(); }
+ //! Returns the number of elements (=nesting level) in the path.
size_t size() const { return path_.size(); }
+ //! Returns the i'th path element.
const std::string &operator[](int i) const { return path_[i]; }
+ //! Returns all the path elements.
const std::vector<std::string> &elements() const { return path_; }
+ //! Formats the path as a string for display.
std::string toString() const;
private:
bool isType() const { return value_.isType<T>(); }
std::type_index type() const { return value_.type(); }
+ KeyValueTreeArray &asArray();
+ KeyValueTreeObject &asObject();
const KeyValueTreeArray &asArray() const;
const KeyValueTreeObject &asObject() const;
template <typename T>
private:
explicit KeyValueTreeValue(Variant &&value) : value_(std::move(value)) {}
- KeyValueTreeArray &asArray();
- KeyValueTreeObject &asObject();
-
Variant value_;
friend class KeyValueTreeBuilder;
return valueMap_.at(key);
}
+ bool hasDistinctProperties(const KeyValueTreeObject &obj) const;
void writeUsing(TextWriter *writer) const;
private:
std::map<std::string, KeyValueTreeValue>::iterator
addProperty(const std::string &key, KeyValueTreeValue &&value)
{
+ GMX_RELEASE_ASSERT(!keyExists(key), "Duplicate key value");
values_.reserve(values_.size() + 1);
auto iter = valueMap_.insert(std::make_pair(key, std::move(value))).first;
values_.push_back(KeyValueTreeProperty(iter));
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017, 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.
#include <functional>
#include <map>
#include <memory>
+#include <typeindex>
#include <vector>
#include "gromacs/utility/exceptions.h"
void setMapping(const KeyValueTreePath &path,
const KeyValueTreeValue &value)
{
+ GMX_RELEASE_ASSERT(sourcePath_.empty(),
+ "Multiple entries map to same path");
if (value.isObject())
{
const KeyValueTreeObject &object = value.asObject();
for (const auto &prop : object.properties())
{
+ GMX_RELEASE_ASSERT(!prop.value().isObject(),
+ "Nested objects not implemented");
childEntries_[prop.key()] = Entry(path);
}
}
typedef std::map<std::string, Rule, StringCompare> ChildRuleMap;
explicit Rule(StringCompareType keyMatchType)
- : childRules_(keyMatchType)
+ : expectedType_(typeid(void)), childRules_(keyMatchType)
{
}
KeyValueTreePath targetPath_;
std::string targetKey_;
+ std::type_index expectedType_;
TransformFunction transform_;
ChildRuleMap childRules_;
};
void KeyValueTreeTransformerImpl::Transformer::doTransform(
const Rule *rule, const KeyValueTreeValue &value)
{
- if (rule->transform_)
+ if (rule->transform_ != nullptr)
{
KeyValueTreeValueBuilder valueBuilder;
try
{
+ if (value.type() != rule->expectedType_)
+ {
+ // TODO: Better error message.
+ GMX_THROW(InvalidInputError("Unexpected type of value"));
+ }
rule->transform_(&valueBuilder, value);
}
catch (UserInputError &ex)
{
if (objBuilder.keyExists(key))
{
+ GMX_RELEASE_ASSERT(objBuilder.getValue(key).isObject(),
+ "Inconsistent transform (different items map to same path)");
objBuilder = objBuilder.getObject(key);
}
else
mapEntry->setMapping(context_, value);
if (objBuilder.keyExists(rule->targetKey_))
{
- objBuilder.getObject(rule->targetKey_).mergeObject(std::move(value));
+ GMX_RELEASE_ASSERT(value.isObject(),
+ "Inconsistent transform (different items map to same path)");
+ GMX_RELEASE_ASSERT(objBuilder.getValue(rule->targetKey_).isObject(),
+ "Inconsistent transform (different items map to same path)");
+ objBuilder = objBuilder.getObject(rule->targetKey_);
+ GMX_RELEASE_ASSERT(objBuilder.objectHasDistinctProperties(value.asObject()),
+ "Inconsistent transform (different items map to same path)");
+ objBuilder.mergeObject(std::move(value));
}
else
{
public:
typedef internal::KeyValueTreeTransformerImpl::Rule Rule;
- Data() : keyMatchType_(StringCompareType::Exact) {}
+ Data()
+ : expectedType_(typeid(void)),
+ keyMatchType_(StringCompareType::Exact), keyMatchRule_(false)
+ {
+ }
void createRule(internal::KeyValueTreeTransformerImpl *impl)
{
- if (toPath_.empty())
+ if (keyMatchRule_)
{
createRuleWithKeyMatchType(impl);
return;
}
+ GMX_RELEASE_ASSERT(transform_ != nullptr,
+ "Transform has not been specified");
Rule *rule = impl->getOrCreateRootRule();
for (const std::string &key : fromPath_.elements())
{
+ GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
+ "Cannot specify multiple rules from a single path");
rule = rule->getOrCreateChildRule(key);
}
- rule->targetKey_ = toPath_.pop_last();
- rule->targetPath_ = std::move(toPath_);
- rule->transform_ = transform_;
+ GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
+ "Cannot specify multiple rules from a single path");
+ rule->targetKey_ = toPath_.pop_last();
+ rule->targetPath_ = std::move(toPath_);
+ rule->expectedType_ = expectedType_;
+ rule->transform_ = transform_;
}
void createRuleWithKeyMatchType(internal::KeyValueTreeTransformerImpl *impl)
KeyValueTreePath fromPath_;
KeyValueTreePath toPath_;
+ std::type_index expectedType_;
Rule::TransformFunction transform_;
StringCompareType keyMatchType_;
+ bool keyMatchRule_;
};
/********************************************************************
}
}
-void KeyValueTreeTransformRuleBuilder::setFromPath(const std::string &path)
+void KeyValueTreeTransformRuleBuilder::setFromPath(const KeyValueTreePath &path)
{
data_->fromPath_ = path;
}
-void KeyValueTreeTransformRuleBuilder::setToPath(const std::string &path)
+void KeyValueTreeTransformRuleBuilder::setExpectedType(const std::type_index &type)
+{
+ data_->expectedType_ = type;
+}
+
+void KeyValueTreeTransformRuleBuilder::setToPath(const KeyValueTreePath &path)
{
data_->toPath_ = path;
}
void KeyValueTreeTransformRuleBuilder::setKeyMatchType(StringCompareType keyMatchType)
{
data_->keyMatchType_ = keyMatchType;
+ data_->keyMatchRule_ = true;
}
void KeyValueTreeTransformRuleBuilder::addTransformToVariant(
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017, 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.
* \brief
* Declares utilities for transforming key-value trees.
*
+ * See \ref page_mdmodules for the main use case that these support.
+ *
* \author Teemu Murtola <teemu.murtola@gmail.com>
* \inlibraryapi
* \ingroup module_utility
#include <functional>
#include <string>
+#include <typeindex>
#include <vector>
#include "gromacs/utility/classhelpers.h"
enum class StringCompareType;
+class KeyValueTreeTransformResult;
class KeyValueTreeTransformRuleBuilder;
namespace internal
class KeyValueTreeTransformerImpl;
}
+/*! \libinternal \brief
+ * Interface to declare rules for transforming key-value trees.
+ *
+ * This interface is used to add transformation rules for key-value trees.
+ * A transformation is a set of rules that is used to map an input key-value
+ * tree to an output key-value tree, with possible conversion steps performed
+ * in the process. Currently, each rule maps one item from the source tree to
+ * one item in the target tree (it is possible to expand a single value into an
+ * object with multiple properties). See KeyValueTreeTransformRuleBuilder for
+ * the kinds of rules currently supported.
+ *
+ * The main use currently is in converting flat-format mdp files to a
+ * structured internal representation.
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
class IKeyValueTreeTransformRules
{
public:
+ /*! \brief
+ * Creates a new rule.
+ *
+ * Properties of the new rule must be specified using the returned
+ * builder.
+ */
virtual KeyValueTreeTransformRuleBuilder addRule() = 0;
protected:
~IKeyValueTreeTransformRules();
};
-class IKeyValueTreeBackMapping
-{
- public:
- virtual ~IKeyValueTreeBackMapping();
-
- virtual KeyValueTreePath
- originalPath(const KeyValueTreePath &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:
- KeyValueTreeTransformer();
- ~KeyValueTreeTransformer();
-
- IKeyValueTreeTransformRules *rules();
-
- std::vector<KeyValueTreePath> mappedPaths() const;
-
- KeyValueTreeTransformResult
- transform(const KeyValueTreeObject &tree,
- IKeyValueTreeErrorHandler *errorHandler) const;
-
- private:
- PrivateImplPointer<internal::KeyValueTreeTransformerImpl> impl_;
-};
-
+/*! \libinternal \brief
+ * Provides methods to specify one transformation rule.
+ *
+ * \if internal
+ * The builder is implemented as a set of nested objects, each of which is
+ * provides methods for setting a particular property of the rule. Setting a
+ * property returns another object that has relevant methods for the context.
+ * This provides some structure to the methods, and catches at least some types
+ * of incorrect rules already at compile time.
+ * Additionally, if you use an IDE with completion facilities, it can nicely
+ * guide you through which values you need to specify.
+ * All values are stored within the main builder object, and the rule is
+ * created at the end of the statement.
+ * \endif
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
class KeyValueTreeTransformRuleBuilder
{
public:
+ /*! \internal \brief
+ * Base class used for implementing parameter provider objects.
+ */
class Base
{
protected:
+ //! Creates a parameter provider object within given builder.
explicit Base(KeyValueTreeTransformRuleBuilder *builder)
: builder_(builder)
{
}
+ //! The parent builder.
KeyValueTreeTransformRuleBuilder *builder_;
};
+ /*! \libinternal \brief
+ * Properties that can be specified after from().to().
+ *
+ * \tparam FromType Type specified for from() to map from.
+ * \tparam ToType Type specified for to() to map to.
+ */
template <typename FromType, typename ToType>
class ToValue : public Base
{
public:
+ //! Creates a parameter provider object within given builder.
explicit ToValue(KeyValueTreeTransformRuleBuilder *builder)
: Base(builder)
{
}
+ /*! \brief
+ * Specifies the transformation function to convert the value
+ * from FromType to ToType.
+ */
void transformWith(std::function<ToType(const FromType &)> transform)
{
builder_->addTransformToVariant(
}
};
+ /*! \libinternal \brief
+ * Properties that can be specified after from().toObject().
+ *
+ * \tparam FromType Type specified for from() to map from.
+ */
template <typename FromType>
class ToObject : public Base
{
public:
+ //! Creates a parameter provider object within given builder.
explicit ToObject(KeyValueTreeTransformRuleBuilder *builder)
: Base(builder)
{
}
+ /*! \brief
+ * Specifies the transformation function to build the output
+ * object.
+ *
+ * The transform should build the output object with the
+ * provided builder.
+ */
void transformWith(std::function<void(KeyValueTreeObjectBuilder *, const FromType &)> transform)
{
builder_->addTransformToObject(
}
};
+ /*! \libinternal \brief
+ * Properties that can be specified after from().
+ *
+ * \tparam FromType Type specified for from() to map from.
+ */
template <typename FromType>
class AfterFrom : public Base
{
public:
+ //! Creates a parameter provider object within given builder.
explicit AfterFrom(KeyValueTreeTransformRuleBuilder *builder)
: Base(builder)
{
}
+ /*! \brief
+ * Specifies a rule that maps to a value at given path.
+ *
+ * \tparam ToType Type to map to.
+ * \param[in] path Path to map to.
+ *
+ * It is an error if multiple rules map to the same path, or to
+ * a parent path of the target of an existing rule.
+ * Note that it is possible to have a to() rule map to a child
+ * of a toObject() rule, provided that the path is not created
+ * by the object rule.
+ */
template <typename ToType>
- ToValue<FromType, ToType> to(const std::string &path)
+ ToValue<FromType, ToType> to(const KeyValueTreePath &path)
{
builder_->setToPath(path);
return ToValue<FromType, ToType>(builder_);
}
- ToObject<FromType> toObject(const std::string &path)
+ /*! \brief
+ * Specifies a rule that maps to an object (collection of named
+ * values) at given path.
+ *
+ * \param[in] path Path to map to.
+ *
+ * It is an error if multiple rules map to the same path, or to
+ * a parent path of the target of an existing rule.
+ * However, it is allowed to have two toObject() rules map to
+ * the same path, provided that the properties they produce are
+ * distinct.
+ */
+ ToObject<FromType> toObject(const KeyValueTreePath &path)
{
builder_->setToPath(path);
return ToObject<FromType>(builder_);
}
};
+ //! Internal constructor for creating a builder.
explicit KeyValueTreeTransformRuleBuilder(internal::KeyValueTreeTransformerImpl *impl);
+ //! Supports returning the builder from IKeyValueTreeTransformRules::addRule().
KeyValueTreeTransformRuleBuilder(KeyValueTreeTransformRuleBuilder &&) = default;
+ //! Supports returning the builder from IKeyValueTreeTransformRules::addRule().
KeyValueTreeTransformRuleBuilder &operator=(KeyValueTreeTransformRuleBuilder &&) = default;
~KeyValueTreeTransformRuleBuilder();
+ /*! \brief
+ * Specifies a rule that maps a value at given path.
+ *
+ * \tparam FromType Type of value expected at `path`.
+ * \param[in] path Path to map in this rule.
+ *
+ * If the input tree has `path`, but it is not of type `FromType`,
+ * the transform will produce an error.
+ *
+ * It is an error to use the same path in two from() rules. Similarly,
+ * it is an error to use a child path of a path used in a different
+ * from() rule.
+ */
template <typename FromType>
- AfterFrom<FromType> from(const std::string &path)
+ AfterFrom<FromType> from(const KeyValueTreePath &path)
{
setFromPath(path);
+ setExpectedType(typeid(FromType));
return AfterFrom<FromType>(this);
}
- void keyMatchType(const std::string &path, StringCompareType keyMatchType)
+ /*! \brief
+ * Specifies how strings are matched when matching rules against a path.
+ *
+ * For properties of the object at `path`, `keyMatchType` is used for
+ * string comparison.
+ *
+ * This rule must be specified first for a path, before any other
+ * from() rule specifies the path or a subpath.
+ * The rule only applies to immediate properties at the given path, not
+ * recursively.
+ * It is an error to specify the match type multiple times for a path.
+ */
+ void keyMatchType(const KeyValueTreePath &path, StringCompareType keyMatchType)
{
setFromPath(path);
setKeyMatchType(keyMatchType);
}
private:
- void setFromPath(const std::string &path);
- void setToPath(const std::string &path);
+ void setFromPath(const KeyValueTreePath &path);
+ void setExpectedType(const std::type_index &type);
+ void setToPath(const KeyValueTreePath &path);
void setKeyMatchType(StringCompareType keyMatchType);
void addTransformToVariant(std::function<Variant(const Variant &)> transform);
void addTransformToObject(std::function<void(KeyValueTreeObjectBuilder *, const Variant &)> transform);
std::unique_ptr<Data> data_;
};
+class KeyValueTreeTransformer
+{
+ public:
+ KeyValueTreeTransformer();
+ ~KeyValueTreeTransformer();
+
+ IKeyValueTreeTransformRules *rules();
+
+ std::vector<KeyValueTreePath> mappedPaths() const;
+
+ KeyValueTreeTransformResult
+ transform(const KeyValueTreeObject &tree,
+ IKeyValueTreeErrorHandler *errorHandler) const;
+
+ private:
+ PrivateImplPointer<internal::KeyValueTreeTransformerImpl> impl_;
+};
+
+class IKeyValueTreeBackMapping
+{
+ public:
+ virtual ~IKeyValueTreeBackMapping();
+
+ virtual KeyValueTreePath
+ originalPath(const KeyValueTreePath &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_;
+ MappingPointer mapping_;
+
+ friend class internal::KeyValueTreeTransformerImpl;
+};
+
} // namespace gmx
#endif