More documentation for key-value trees
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 25 Feb 2017 19:27:42 +0000 (21:27 +0200)
committerDavid van der Spoel <davidvanderspoel@gmail.com>
Tue, 28 Feb 2017 13:12:43 +0000 (14:12 +0100)
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
src/gromacs/utility/keyvaluetreebuilder.h
src/gromacs/utility/keyvaluetreetransform.cpp

index 0da98a390b49360cd449677225e18ab9bb226505..b7afb9dc88a1104a5c697159edf07b784952f8dd 100644 (file)
  * \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 <teemu.murtola@gmail.com>
  * \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 <typename T>
         bool isType() const { return value_.isType<T>(); }
+        //! Returns the type of the value.
         std::type_index type() const { return value_.type(); }
 
         KeyValueTreeArray &asArray();
@@ -134,6 +159,7 @@ class KeyValueTreeValue
         template <typename T>
         const T                  &cast() const { return value_.cast<T>(); }
 
+        //! 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<KeyValueTreeValue> &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<KeyValueTreeProperty> &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<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));
-            return iter;
-        }
-
+        //! Keeps the properties by key.
         std::map<std::string, KeyValueTreeValue> valueMap_;
+        //! Keeps the insertion order of properties.
         std::vector<KeyValueTreeProperty>        values_;
 
         friend class KeyValueTreeObjectBuilder;
index 4fd4ff3af3d62963a1a0d21d6576e792f39db026..be52edb47e2c8e3969de864b5b4cc95f4acd6886 100644 (file)
  * \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 <teemu.murtola@gmail.com>
  * \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 <typename T>
         static KeyValueTreeValue createValue(const T &value)
         {
             return KeyValueTreeValue(Variant::create<T>(value));
         }
+        /*! \brief
+         * Helper function for other builders to create default-constructed
+         * values.
+         */
         template <typename T>
         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 <typename T>
         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 <typename T>
         void setValue(const T &value)
         {
             value_ = Variant::create<T>(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 <typename T>
 class KeyValueTreeUniformArrayBuilder : public KeyValueTreeArrayBuilderBase
 {
     public:
+        //! Appends a value to the array.
         void addValue(const T &value)
         {
             addRawValue(KeyValueTreeBuilder::createValue<T>(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 <typename T>
         void addValue(const std::string &key, const T &value)
         {
             addRawValue(key, KeyValueTreeBuilder::createValue<T>(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<KeyValueTreeObject>());
+            auto iter = addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeObject>());
             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<KeyValueTreeArray>());
+            auto iter = addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeArray>());
             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 <typename T>
         KeyValueTreeUniformArrayBuilder<T> addUniformArray(const std::string &key)
         {
-            auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeArray>());
+            auto iter = addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeArray>());
             return KeyValueTreeUniformArrayBuilder<T>(&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<KeyValueTreeArray>());
+            auto iter = addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeArray>());
             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<std::string, KeyValueTreeValue>::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;
index a204819f896efab0a84cb16a1c890de1c0884630..409bf0189c8cab6c7b03c790b40fd3a3c2924840 100644 (file)
@@ -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
     {