* \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
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();
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:
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:
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_)
values_.push_back(KeyValueTreeProperty(iter));
}
}
+ //! Assigns a deep copy of an object.
KeyValueTreeObject &operator=(KeyValueTreeObject &other)
{
KeyValueTreeObject tmp(other);
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;
* \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
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()
{
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:
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;
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));
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));
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:
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)
{
}
+ 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;