/* * \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 internal representation 'ELEM' carries two methods 'matches' * and 'type_matches' that define the relation of the elements to XML nodes. * E.g., * * ! struct Item : List_model::Element * ! { * ! static bool type_matches(Xml_node const &); * ! * ! bool matches(Xml_node const &) const; * ! ... * ! }; * * The class function 'type_matches' returns true if the specified XML node * matches the 'Item' type. It can thereby be used to control the creation * of 'ELEM' nodes by responding to specific XML tags while ignoring unrelated * XML tags. * * The 'matches' method returns true if the concrete element instance matches * the given XML node. It is used to correlate existing 'ELEM' objects with * new versions of XML nodes to update the 'ELEM' objects. * * The functor arguments 'create_fn', 'destroy_fn', and 'update_fn' for the * 'update_from_xml' method define how objects are created, destructed, and * updated. E.g., * * ! _list_model.update_from_xml(node, * ! * ! [&] (Xml_node const &node) -> Item & { * ! return *new (alloc) Item(node); }, * ! * ! [&] (Item &item) { destroy(alloc, &item); }, * ! * ! [&] (Item &item, Xml_node const &node) { item.update(node); } * ! ); * * The elements are ordered according to the order of XML nodes. * * The list model is a container owning the elements. Before destructing a * list model, its elements must be removed by calling 'update_from_xml' * with an 'Xml_node("")' as argument, which results in the call * of 'destroy_fn' for each element. */ /* * Copyright (C) 2017-2023 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 #include #include #include namespace Genode { template class List_model; } template class Genode::List_model : Noncopyable { private: List _elements { }; public: struct Update_policy; class Element : private List::Element { private: /* used by 'List_model::update_from_xml' only */ friend class List_model; friend class List; ELEM *_next() const { return List::Element::next(); } /** * Noncopyable */ Element(Element const &) = delete; Element & operator = (Element const &) = delete; public: Element() { }; /** * Return the element's neighbor if present, otherwise nullptr */ ELEM const *next() const { return List::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 the given XML node */ inline void update_from_xml(Xml_node const &, auto const &create_fn, auto const &destroy_fn, auto const &update_fn); /** * Call functor 'fn' for each const element */ void for_each(auto const &fn) const { for (Element const *e = _elements.first(); e; e = e->next()) fn(static_cast(*e)); } /** * Call functor 'fn' for each non-const element */ void for_each(auto const &fn) { Element *next = nullptr; for (Element *e = _elements.first(); e; e = next) { next = e->_next(); fn(static_cast(*e)); } } /** * Call functor 'fn' with the first element of the list model * * Using this method combined with 'Element::next', 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. */ void with_first(auto const &fn) const { if (Element const *e = _elements.first()) fn(static_cast(*e)); } }; template void Genode::List_model::update_from_xml(Xml_node const &node, auto const &create_fn, auto const &destroy_fn, auto const &update_fn) { List updated_list; ELEM *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 (!ELEM::type_matches(sub_node)) return; /* check for duplicates, which must not exist in the list model */ for (ELEM *dup = updated_list.first(); dup; dup = dup->_next()) { /* update existing element with information from later node */ if (dup->matches(sub_node)) { update_fn(*dup, sub_node); return; } } /* look up corresponding element in original list */ ELEM *curr = _elements.first(); while (curr && !curr->matches(sub_node)) curr = curr->_next(); /* consume existing element or create new one */ if (curr) _elements.remove(curr); else curr = &create_fn(sub_node); /* append current element to 'updated_list' */ updated_list.insert(curr, last_updated); last_updated = curr; update_fn(*curr, sub_node); }); /* remove stale elements */ ELEM *next = nullptr; for (ELEM *e = _elements.first(); e; e = next) { next = e->_next(); destroy_fn(*e); } /* use 'updated_list' list new data model */ _elements = updated_list; } #endif /* _INCLUDE__UTIL__LIST_MODEL_H_ */