genode/repos/base/include/util/list_model.h

249 lines
6.1 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 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; }
};
#endif /* _INCLUDE__UTIL__LIST_MODEL_H_ */