From 5b56869cf1603b7d162270a5f79b749443b61a93 Mon Sep 17 00:00:00 2001 From: Teemu Murtola Date: Sat, 25 Feb 2017 21:27:42 +0200 Subject: [PATCH] More documentation for key-value trees Document most of the builder APIs for key-value trees, and some of the functions in the data structures themselves. This should cover most of the functionality that is currently visible to IInputRecExtension modules. Some clean-up and additional asserts for the builder API. Change-Id: Ifa0d86bcd62661616008ec61db47ddd0ba7a6ead --- src/gromacs/utility/keyvaluetree.h | 68 +++++++-- src/gromacs/utility/keyvaluetreebuilder.h | 193 +++++++++++++++++++++++--- src/gromacs/utility/keyvaluetreetransform.cpp | 10 +- 3 files changed, 231 insertions(+), 40 deletions(-) diff --git a/src/gromacs/utility/keyvaluetree.h b/src/gromacs/utility/keyvaluetree.h index 0da98a390b..b7afb9dc88 100644 --- a/src/gromacs/utility/keyvaluetree.h +++ b/src/gromacs/utility/keyvaluetree.h @@ -36,6 +36,27 @@ * \brief * Declares a data structure for JSON-like structured key-value mapping. * + * A tree is composed of nodes that can have different types: + * - _Value_ (gmx::KeyValueTreeValue) is a generic node that can + * represent either a scalar value of arbitrary type, or an object or + * an array. + * - _Array_ (gmx::KeyValueTreeArray) is a collection of any number of values + * (including zero). The values can be of any type and different types + * can be mixed in the same array. + * - _Object_ (gmx::KeyValueTreeObject) is a collection of properties. + * Each property must have a unique key. Order of properties is preserved, + * i.e., they can be iterated in the order they were added. + * - _Property_ (gmx::KeyValueTreeProperty) is an arbitrary type of value + * associated with a string key. + * The root object of a tree is typically an object, but could also be an + * array. The data structure itself does not enforce any other constraints, + * but the context in which it is used can limit the allowed scalar types or, + * e.g., require arrays to have values of uniform type. Also, several + * operations defined for the structure (string output, comparison, + * serialization, etc.) only work on a limited set of scalar types, or have + * limitations with the types of trees they work on (in particular, arrays are + * currently poorly supported). + * * \author Teemu Murtola * \inlibraryapi * \ingroup module_utility @@ -121,10 +142,14 @@ class KeyValueTreePath class KeyValueTreeValue { public: + //! Returns whether the value is an array (KeyValueTreeArray). bool isArray() const; + //! Returns whether the value is an object (KeyValueTreeObject). bool isObject() const; + //! Returns whether the value is of a given type. template bool isType() const { return value_.isType(); } + //! Returns the type of the value. std::type_index type() const { return value_.type(); } KeyValueTreeArray &asArray(); @@ -134,6 +159,7 @@ class KeyValueTreeValue template const T &cast() const { return value_.cast(); } + //! Returns the raw Variant value (always possible). const Variant &asVariant() const { return value_; } private: @@ -149,12 +175,14 @@ class KeyValueTreeValue class KeyValueTreeArray { public: + //! Whether all elements of the array are objects. bool isObjectArray() const { return std::all_of(values_.begin(), values_.end(), std::mem_fn(&KeyValueTreeValue::isObject)); } + //! Returns the values in the array. const std::vector &values() const { return values_; } private: @@ -178,12 +206,14 @@ class KeyValueTreeProperty IteratorType value_; friend class KeyValueTreeObject; + friend class KeyValueTreeObjectBuilder; }; class KeyValueTreeObject { public: KeyValueTreeObject() = default; + //! Creates a deep copy of an object. KeyValueTreeObject(const KeyValueTreeObject &other) { for (const auto &value : other.values_) @@ -192,6 +222,7 @@ class KeyValueTreeObject values_.push_back(KeyValueTreeProperty(iter)); } } + //! Assigns a deep copy of an object. KeyValueTreeObject &operator=(KeyValueTreeObject &other) { KeyValueTreeObject tmp(other); @@ -199,39 +230,48 @@ class KeyValueTreeObject std::swap(tmp.values_, values_); return *this; } + //! Default move constructor. KeyValueTreeObject(KeyValueTreeObject &&) = default; + //! Default move assignment. KeyValueTreeObject &operator=(KeyValueTreeObject &&) = default; + /*! \brief + * Returns all properties in the object. + * + * The properties are in the order they were added to the object. + */ const std::vector &properties() const { return values_; } + //! Whether a property with given key exists. bool keyExists(const std::string &key) const { return valueMap_.find(key) != valueMap_.end(); } + //! Returns value for a given key. const KeyValueTreeValue &operator[](const std::string &key) const { + GMX_ASSERT(keyExists(key), "Accessing non-existent value"); return valueMap_.at(key); } + /*! \brief + * Returns whether the given object shares any keys with `this`. + */ bool hasDistinctProperties(const KeyValueTreeObject &obj) const; + + /*! \brief + * Writes a string representation of the object with given writer. + * + * The output format is designed to be readable by humans; if some + * particular machine-readable format is needed, that should be + * implemented outside the generic key-value tree code. + */ void writeUsing(TextWriter *writer) const; private: - KeyValueTreeValue &operator[](const std::string &key) - { - return valueMap_.at(key); - } - std::map::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)); - return iter; - } - + //! Keeps the properties by key. std::map valueMap_; + //! Keeps the insertion order of properties. std::vector values_; friend class KeyValueTreeObjectBuilder; diff --git a/src/gromacs/utility/keyvaluetreebuilder.h b/src/gromacs/utility/keyvaluetreebuilder.h index 4fd4ff3af3..be52edb47e 100644 --- a/src/gromacs/utility/keyvaluetreebuilder.h +++ b/src/gromacs/utility/keyvaluetreebuilder.h @@ -36,6 +36,14 @@ * \brief * Declares classes for building the data structures in keyvaluetree.h. * + * These are separate from the data structures to enforce clear separation of + * the APIs, and to make the data structure immutable after construction. + * + * For the main use case described in \ref page_mdmodules, they are mainly + * used internally, but currently gmx::KeyValueTreeObjectBuilder (and + * everything it references) is exposed for more complex transforms through + * gmx::IKeyValueTreeTransformRules. + * * \author Teemu Murtola * \inlibraryapi * \ingroup module_utility @@ -57,19 +65,38 @@ namespace gmx class KeyValueTreeArrayBuilder; class KeyValueTreeObjectBuilder; +/*! \libinternal \brief + * Root builder for creating trees that have an object at the root. + * + * \inlibraryapi + * \ingroup module_utility + */ class KeyValueTreeBuilder { public: + //! Returns a builder for the root object. KeyValueTreeObjectBuilder rootObject(); + /*! \brief + * Builds the final object. + * + * The builder should not be accessed after this call. + */ KeyValueTreeObject build() { return std::move(root_); } private: + /*! \brief + * Helper function for other builders to create values of certain type. + */ template static KeyValueTreeValue createValue(const T &value) { return KeyValueTreeValue(Variant::create(value)); } + /*! \brief + * Helper function for other builders to create default-constructed + * values. + */ template static KeyValueTreeValue createValue() { @@ -78,27 +105,58 @@ class KeyValueTreeBuilder KeyValueTreeObject root_; + //! For access to createValue() methods. friend class KeyValueTreeObjectArrayBuilder; + //! For access to createValue() methods. friend class KeyValueTreeObjectBuilder; + //! For access to createValue() methods. template friend class KeyValueTreeUniformArrayBuilder; }; +/*! \libinternal \brief + * Builder for KeyValueTreeValue objects. + * + * This builder can be constructed directly and can create self-standing + * KeyValueTreeValue objects. + * + * \inlibraryapi + * \ingroup module_utility + */ class KeyValueTreeValueBuilder { public: + //! Assigns a scalar value of certain type. template void setValue(const T &value) { value_ = Variant::create(value); } + //! Assigns a Variant value to the built value. void setVariantValue(Variant &&value) { value_ = std::move(value); } + /*! \brief + * Returns an object builder for building an object into this value. + * + * Any method call in this value builder invalidates the returned + * builder. + */ KeyValueTreeObjectBuilder createObject(); + /*! \brief + * Returns an array builder for building an array into this value. + * + * Any method call in this value builder invalidates the returned + * builder. + */ KeyValueTreeArrayBuilder createArray(); + /*! \brief + * Builds the final value. + * + * The builder should not be accessed after this call. + */ KeyValueTreeValue build() { return KeyValueTreeValue(std::move(value_)); } private: @@ -108,11 +166,13 @@ class KeyValueTreeValueBuilder class KeyValueTreeArrayBuilderBase { protected: + //! Creates an array builder for populating given array object. explicit KeyValueTreeArrayBuilderBase(KeyValueTreeArray *array) : array_(array) { } + //! Appends a raw Variant value to the array. KeyValueTreeValue &addRawValue(Variant &&value) { KeyValueTreeValueBuilder builder; @@ -120,6 +180,7 @@ class KeyValueTreeArrayBuilderBase array_->values_.push_back(builder.build()); return array_->values_.back(); } + //! Appends a raw KeyValueTreeValue to the array. KeyValueTreeValue &addRawValue(KeyValueTreeValue &&value) { array_->values_.push_back(std::move(value)); @@ -145,10 +206,21 @@ class KeyValueTreeArrayBuilder : public KeyValueTreeArrayBuilderBase friend class KeyValueTreeValueBuilder; }; +/*! \libinternal \brief + * Builder for KeyValueTreeArray objects where all elements are of type `T`. + * + * The builder does not own the array being constructed, but instead holds a + * reference to an object within a tree rooted in KeyValueTreeBuilder or + * KeyValueTreeValueBuilder. + * + * \inlibraryapi + * \ingroup module_utility + */ template class KeyValueTreeUniformArrayBuilder : public KeyValueTreeArrayBuilderBase { public: + //! Appends a value to the array. void addValue(const T &value) { addRawValue(KeyValueTreeBuilder::createValue(value)); @@ -163,9 +235,26 @@ class KeyValueTreeUniformArrayBuilder : public KeyValueTreeArrayBuilderBase friend class KeyValueTreeObjectBuilder; }; +/*! \libinternal \brief + * Builder for KeyValueTreeArray objects where all elements are + * KeyValueTreeObject objects. + * + * The builder does not own the array being constructed, but instead holds a + * reference to an object within a tree rooted in KeyValueTreeBuilder or + * KeyValueTreeValueBuilder. + * + * \inlibraryapi + * \ingroup module_utility + */ class KeyValueTreeObjectArrayBuilder : public KeyValueTreeArrayBuilderBase { public: + /*! \brief + * Appends an object to the array. + * + * The object is created empty and can be built using the returned + * builder. + */ KeyValueTreeObjectBuilder addObject(); private: @@ -177,71 +266,123 @@ class KeyValueTreeObjectArrayBuilder : public KeyValueTreeArrayBuilderBase friend class KeyValueTreeObjectBuilder; }; +/*! \libinternal \brief + * Builder for KeyValueTreeObject objects. + * + * The builder does not own the object being constructed, but instead holds a + * reference to an object within a tree rooted in KeyValueTreeBuilder or + * KeyValueTreeValueBuilder. + * + * \inlibraryapi + * \ingroup module_utility + */ class KeyValueTreeObjectBuilder { public: + //! Adds a property with given key from a KeyValueTreeValue. void addRawValue(const std::string &key, KeyValueTreeValue &&value) { - object_->addProperty(key, std::move(value)); + addProperty(key, std::move(value)); } + //! Adds a property with given key from a Variant value. void addRawValue(const std::string &key, Variant &&value) { - object_->addProperty(key, KeyValueTreeValue(std::move(value))); + addProperty(key, KeyValueTreeValue(std::move(value))); } + //! Adds a scalar property with given key, type, and value. template void addValue(const std::string &key, const T &value) { addRawValue(key, KeyValueTreeBuilder::createValue(value)); } + /*! \brief + * Adds an object-valued property with given key. + * + * The object is created empty and can be built using the returned + * builder. + */ KeyValueTreeObjectBuilder addObject(const std::string &key) { - auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue()); + auto iter = addProperty(key, KeyValueTreeBuilder::createValue()); return KeyValueTreeObjectBuilder(&iter->second); } + /*! \brief + * Adds a generic array-valued property with given key. + * + * The array is created empty and can be built using the returned + * builder. + */ KeyValueTreeArrayBuilder addArray(const std::string &key) { - auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue()); + auto iter = addProperty(key, KeyValueTreeBuilder::createValue()); return KeyValueTreeArrayBuilder(&iter->second.asArray()); } + /*! \brief + * Adds an array-valued property with uniform value types with given + * key. + * + * \tparam T Type for all values in the array. + * + * The array is created empty and can be built using the returned + * builder. + */ template KeyValueTreeUniformArrayBuilder addUniformArray(const std::string &key) { - auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue()); + auto iter = addProperty(key, KeyValueTreeBuilder::createValue()); return KeyValueTreeUniformArrayBuilder(&iter->second.asArray()); } + /*! \brief + * Adds an array-valued property with objects in the array with given + * key. + * + * The array is created empty and can be built using the returned + * builder. + */ KeyValueTreeObjectArrayBuilder addObjectArray(const std::string &key) { - auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue()); + auto iter = addProperty(key, KeyValueTreeBuilder::createValue()); return KeyValueTreeObjectArrayBuilder(&iter->second.asArray()); } - bool objectHasDistinctProperties(const KeyValueTreeObject &obj) const + //! Whether a property with given key exists. + bool keyExists(const std::string &key) const { return object_->keyExists(key); } + //! Returns value for a given key. + const KeyValueTreeValue &operator[](const std::string &key) const { - return object_->hasDistinctProperties(obj); + return (*object_)[key]; } - void mergeObject(KeyValueTreeValue &&value) + //! Returns an object builder for an existing object. + KeyValueTreeObjectBuilder getObjectBuilder(const std::string &key) { - mergeObject(std::move(value.asObject())); + GMX_ASSERT(keyExists(key), "Requested non-existent value"); + GMX_ASSERT((*this)[key].isObject(), "Accessing non-object value as object"); + return KeyValueTreeObjectBuilder(&object_->valueMap_.at(key).asObject()); + } + + /*! \brief + * Returns whether the given object shares any keys with \p this. + */ + bool objectHasDistinctProperties(const KeyValueTreeObject &obj) const + { + return object_->hasDistinctProperties(obj); } + /*! \brief + * Merges properties from a given object to `this`. + * + * The objects should not share any keys, i.e., + * objectHasDistinctProperties() should return `true`. + */ void mergeObject(KeyValueTreeObject &&obj) { + GMX_ASSERT(objectHasDistinctProperties(obj), + "Trying to merge overlapping object"); for (auto &prop : obj.valueMap_) { addRawValue(prop.first, std::move(prop.second)); } } - bool keyExists(const std::string &key) const { return object_->keyExists(key); } - const KeyValueTreeValue &getValue(const std::string &key) const - { - GMX_ASSERT(keyExists(key), "Requested non-existent value"); - return (*object_)[key]; - } - KeyValueTreeObjectBuilder getObject(const std::string &key) - { - return KeyValueTreeObjectBuilder(&(*object_)[key].asObject()); - } - private: explicit KeyValueTreeObjectBuilder(KeyValueTreeObject *object) : object_(object) @@ -252,6 +393,16 @@ class KeyValueTreeObjectBuilder { } + std::map::iterator + addProperty(const std::string &key, KeyValueTreeValue &&value) + { + GMX_RELEASE_ASSERT(!keyExists(key), "Duplicate key value"); + object_->values_.reserve(object_->values_.size() + 1); + auto iter = object_->valueMap_.insert(std::make_pair(key, std::move(value))).first; + object_->values_.push_back(KeyValueTreeProperty(iter)); + return iter; + } + KeyValueTreeObject *object_; friend class KeyValueTreeBuilder; diff --git a/src/gromacs/utility/keyvaluetreetransform.cpp b/src/gromacs/utility/keyvaluetreetransform.cpp index a204819f89..409bf0189c 100644 --- a/src/gromacs/utility/keyvaluetreetransform.cpp +++ b/src/gromacs/utility/keyvaluetreetransform.cpp @@ -336,9 +336,9 @@ void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue( { if (objBuilder.keyExists(key)) { - GMX_RELEASE_ASSERT(objBuilder.getValue(key).isObject(), + GMX_RELEASE_ASSERT(objBuilder[key].isObject(), "Inconsistent transform (different items map to same path)"); - objBuilder = objBuilder.getObject(key); + objBuilder = objBuilder.getObjectBuilder(key); } else { @@ -352,12 +352,12 @@ void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue( { GMX_RELEASE_ASSERT(value.isObject(), "Inconsistent transform (different items map to same path)"); - GMX_RELEASE_ASSERT(objBuilder.getValue(rule->targetKey_).isObject(), + GMX_RELEASE_ASSERT(objBuilder[rule->targetKey_].isObject(), "Inconsistent transform (different items map to same path)"); - objBuilder = objBuilder.getObject(rule->targetKey_); + objBuilder = objBuilder.getObjectBuilder(rule->targetKey_); GMX_RELEASE_ASSERT(objBuilder.objectHasDistinctProperties(value.asObject()), "Inconsistent transform (different items map to same path)"); - objBuilder.mergeObject(std::move(value)); + objBuilder.mergeObject(std::move(value.asObject())); } else { -- 2.11.4.GIT