mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-30 18:47:01 +00:00
4df7e6adde
The new 'update_list_model_from_xml' function template simplifies the use of the list model utility by alleviating the need for implementing a custom policy class for each model. Instead, the transformation is done using a few lambda functions given directly as arguments. Issue #4317
302 lines
7.8 KiB
C++
302 lines
7.8 KiB
C++
/*
|
|
* \brief List-based data model created and updated from XML
|
|
* \author Norman Feske
|
|
* \date 2017-08-09
|
|
*
|
|
* The 'List_model' stores a component-internal representation of XML-node
|
|
* content. The XML information is imported according to an 'Update_policy',
|
|
* which specifies how the elements of the data model are created, destroyed,
|
|
* and updated. The elements are ordered according to the order of XML nodes.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _INCLUDE__UTIL__LIST_MODEL_H_
|
|
#define _INCLUDE__UTIL__LIST_MODEL_H_
|
|
|
|
/* Genode includes */
|
|
#include <util/xml_node.h>
|
|
#include <util/list.h>
|
|
#include <base/log.h>
|
|
|
|
namespace Genode {
|
|
|
|
template <typename> class List_model;
|
|
|
|
template <typename CREATE_FN, typename DESTROY_FN, typename UPDATE_FN,
|
|
typename NODE = decltype(CREATE_FN())>
|
|
static inline void update_list_model_from_xml(List_model<NODE> &,
|
|
Xml_node const &,
|
|
CREATE_FN const &,
|
|
DESTROY_FN const &,
|
|
UPDATE_FN const &);
|
|
}
|
|
|
|
|
|
template <typename ELEM>
|
|
class Genode::List_model
|
|
{
|
|
private:
|
|
|
|
List<ELEM> _elements { };
|
|
|
|
public:
|
|
|
|
struct Update_policy;
|
|
|
|
class Element : private List<ELEM>::Element
|
|
{
|
|
private:
|
|
|
|
/* used by 'List_model::update_from_xml' only */
|
|
friend class List_model;
|
|
friend class List<ELEM>;
|
|
|
|
ELEM *_next() const { return List<ELEM>::Element::next(); }
|
|
|
|
public:
|
|
|
|
/**
|
|
* Return the element's neighbor if present, otherwise nullptr
|
|
*/
|
|
ELEM const *next() const { return List<ELEM>::Element::next(); }
|
|
};
|
|
|
|
struct Unknown_element_type : Exception { };
|
|
|
|
~List_model()
|
|
{
|
|
if (_elements.first())
|
|
warning("list model not empty at destruction time");
|
|
}
|
|
|
|
/**
|
|
* Update data model according to XML structure 'node'
|
|
*
|
|
* \throw Unknown_element_type
|
|
*/
|
|
template <typename POLICY>
|
|
inline void update_from_xml(POLICY &policy, Xml_node node);
|
|
|
|
/**
|
|
* Call functor 'fn' for each const element
|
|
*/
|
|
template <typename FN>
|
|
void for_each(FN const &fn) const
|
|
{
|
|
for (Element const *e = _elements.first(); e; e = e->next())
|
|
fn(static_cast<ELEM const &>(*e));
|
|
}
|
|
|
|
/**
|
|
* Call functor 'fn' for each non-const element
|
|
*/
|
|
template <typename FN>
|
|
void for_each(FN const &fn)
|
|
{
|
|
Element *next = nullptr;
|
|
for (Element *e = _elements.first(); e; e = next) {
|
|
next = e->_next();
|
|
fn(static_cast<ELEM &>(*e));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply functor 'fn' to the first element of the list model
|
|
*
|
|
* Using this method combined with the 'Element::next' method, the list
|
|
* model can be traversed manually. This is handy in situations where
|
|
* the list-model elements are visited via recursive function calls
|
|
* instead of a 'for_each' loop.
|
|
*/
|
|
template <typename FN>
|
|
void apply_first(FN const &fn) const
|
|
{
|
|
if (Element const *e = _elements.first())
|
|
fn(static_cast<ELEM const &>(*e));
|
|
}
|
|
|
|
/**
|
|
* Remove all elements from the data model
|
|
*
|
|
* This method should be called at the destruction time of the
|
|
* 'List_model'.
|
|
*
|
|
* List-model elements are not implicitly destroyed by the destructor
|
|
* because the 'policy' needed to destruct elements is not kept as
|
|
* member of the list model (multiple policies may applied to the same
|
|
* list model).
|
|
*/
|
|
template <typename POLICY>
|
|
void destroy_all_elements(POLICY &policy)
|
|
{
|
|
Element *next = nullptr;
|
|
for (Element *e = _elements.first(); e; e = next) {
|
|
next = e->_next();
|
|
ELEM &elem = static_cast<ELEM &>(*e);
|
|
_elements.remove(&elem);
|
|
policy.destroy_element(elem);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
template <typename ELEM>
|
|
template <typename POLICY>
|
|
void Genode::List_model<ELEM>::update_from_xml(POLICY &policy, Xml_node node)
|
|
{
|
|
typedef typename POLICY::Element Element;
|
|
|
|
List<Element> updated_list;
|
|
|
|
Element *last_updated = nullptr; /* used for appending to 'updated_list' */
|
|
|
|
node.for_each_sub_node([&] (Xml_node sub_node) {
|
|
|
|
/* skip XML nodes that are unrelated to the data model */
|
|
if (!policy.node_is_element(sub_node))
|
|
return;
|
|
|
|
/* check for duplicates, which must not exist in the list model */
|
|
for (Element *dup = updated_list.first(); dup; dup = dup->_next()) {
|
|
|
|
/* update existing element with information from later node */
|
|
if (policy.element_matches_xml_node(*dup, sub_node)) {
|
|
policy.update_element(*dup, sub_node);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* look up corresponding element in original list */
|
|
Element *curr = _elements.first();
|
|
while (curr && !policy.element_matches_xml_node(*curr, sub_node))
|
|
curr = curr->_next();
|
|
|
|
/* consume existing element or create new one */
|
|
if (curr) {
|
|
_elements.remove(curr);
|
|
} else {
|
|
/* \throw Unknown_element_type */
|
|
curr = &policy.create_element(sub_node);
|
|
}
|
|
|
|
/* append current element to 'updated_list' */
|
|
updated_list.insert(curr, last_updated);
|
|
last_updated = curr;
|
|
|
|
policy.update_element(*curr, sub_node);
|
|
});
|
|
|
|
/* remove stale elements */
|
|
Element *next = nullptr;
|
|
for (Element *e = _elements.first(); e; e = next) {
|
|
next = e->_next();
|
|
policy.destroy_element(*e);
|
|
}
|
|
|
|
/* use 'updated_list' list new data model */
|
|
_elements = updated_list;
|
|
}
|
|
|
|
|
|
/**
|
|
* Policy interface to be supplied to 'List_model::update_from_xml'
|
|
*
|
|
* \param ELEM element type, must be a list element
|
|
*
|
|
* This class template is merely a blue print of a policy to document the
|
|
* interface.
|
|
*/
|
|
template <typename ELEM>
|
|
struct Genode::List_model<ELEM>::Update_policy
|
|
{
|
|
typedef List_model<ELEM>::Unknown_element_type Unknown_element_type;
|
|
|
|
/*
|
|
* Type that needs to be supplied by the policy implementation
|
|
*/
|
|
typedef ELEM Element;
|
|
|
|
/**
|
|
* Destroy element
|
|
*
|
|
* When this function is called, the element is no longer contained
|
|
* in the model's list.
|
|
*/
|
|
void destroy_element(ELEM &elem);
|
|
|
|
/**
|
|
* Create element of the type given in the 'elem_node'
|
|
*
|
|
* \throw List_model::Unknown_element_type
|
|
*/
|
|
ELEM &create_element(Xml_node elem_node);
|
|
|
|
/**
|
|
* Import element properties from XML node
|
|
*/
|
|
void update_element(ELEM &elem, Xml_node elem_node);
|
|
|
|
/**
|
|
* Return true if element corresponds to XML node
|
|
*/
|
|
static bool element_matches_xml_node(Element const &, Xml_node);
|
|
|
|
/**
|
|
* Return true if XML node should be imported
|
|
*
|
|
* This method allows the policy to disregard certain XML node types from
|
|
* building the data model.
|
|
*/
|
|
static bool node_is_element(Xml_node) { return true; }
|
|
};
|
|
|
|
|
|
template <typename CREATE_FN, typename DESTROY_FN, typename UPDATE_FN, typename NODE>
|
|
void Genode::update_list_model_from_xml(List_model<NODE> &model,
|
|
Xml_node const &xml,
|
|
CREATE_FN const &create,
|
|
DESTROY_FN const &destroy,
|
|
UPDATE_FN const &update)
|
|
{
|
|
struct Model_update_policy : List_model<NODE>::Update_policy
|
|
{
|
|
CREATE_FN const &_create_fn;
|
|
DESTROY_FN const &_destroy_fn;
|
|
UPDATE_FN const &_update_fn;
|
|
|
|
Model_update_policy(CREATE_FN const &create_fn,
|
|
DESTROY_FN const &destroy_fn,
|
|
UPDATE_FN const &update_fn)
|
|
:
|
|
_create_fn(create_fn), _destroy_fn(destroy_fn), _update_fn(update_fn)
|
|
{ }
|
|
|
|
void destroy_element(NODE &node) { _destroy_fn(node); }
|
|
|
|
NODE &create_element(Xml_node xml) { return _create_fn(xml); }
|
|
|
|
void update_element(NODE &node, Xml_node xml) { _update_fn(node, xml); }
|
|
|
|
static bool element_matches_xml_node(NODE const &node, Xml_node xml)
|
|
{
|
|
return node.matches(xml);
|
|
}
|
|
|
|
static bool node_is_element(Xml_node node)
|
|
{
|
|
return NODE::type_matches(node);
|
|
}
|
|
|
|
} policy(create, destroy, update);
|
|
|
|
model.update_from_xml(policy, xml);
|
|
}
|
|
|
|
#endif /* _INCLUDE__UTIL__LIST_MODEL_H_ */
|