implement cxTreeRemove() with re-link function

Sat, 05 Oct 2024 19:05:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 05 Oct 2024 19:05:47 +0200
changeset 909
f4e00bb3f3b2
parent 908
f49f8a7060aa
child 910
5c2af036103f

implement cxTreeRemove() with re-link function

fixes #437

src/cx/tree.h file | annotate | diff | comparison | revisions
src/tree.c file | annotate | diff | comparison | revisions
tests/test_tree.c file | annotate | diff | comparison | revisions
--- a/src/cx/tree.h	Sat Oct 05 14:42:14 2024 +0200
+++ b/src/cx/tree.h	Sat Oct 05 19:05:47 2024 +0200
@@ -1196,6 +1196,44 @@
 );
 
 /**
+ * A function that is invoked when a node needs to be re-linked to a new parent.
+ *
+ * When a node is re-linked, sometimes the contents need to be updated.
+ * This callback is invoked by #cxTreeRemove() so that those updates can be
+ * applied when re-linking the children of the removed node.
+ *
+ * @param node the affected node
+ * @param old_parent the old parent of the node
+ * @param new_parent the new parent of the node
+ */
+typedef void (*cx_tree_relink_func)(
+        void *node,
+        const void *old_parent,
+        const void *new_parent
+);
+
+/**
+ * Removes a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed node by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if \p node is the root node of the tree
+ */
+__attribute__((__nonnull__(1,2)))
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+);
+
+/**
  * Removes a node and it's subtree from the tree.
  *
  * If the node is not part of the tree, the behavior is undefined.
--- a/src/tree.c	Sat Oct 05 14:42:14 2024 +0200
+++ b/src/tree.c	Sat Oct 05 19:05:47 2024 +0200
@@ -900,10 +900,48 @@
     return visitor.depth;
 }
 
-int cxTreeRemove(CxTree *tree, void *node) {
+int cxTreeRemove(
+        CxTree *tree,
+        void *node,
+        cx_tree_relink_func relink_func
+) {
     if (node == tree->root) return 1;
 
+    // determine the new parent
+    ptrdiff_t loc_parent = tree->loc_parent;
+    void *new_parent = tree_parent(node);
 
+    // first, unlink from the parent
+    cx_tree_unlink(node, cx_tree_node_layout(tree));
+
+    // then relink each child
+    ptrdiff_t loc_children = tree->loc_children;
+    ptrdiff_t loc_next = tree->loc_next;
+    void *child = tree_children(node);
+    while (child != NULL) {
+        // forcibly set the parent to NULL - we do not use the unlink function
+        // because that would unnecessarily modify the children linked list
+        tree_parent(child) = NULL;
+
+        // update contents, if required
+        if (relink_func != NULL) {
+            relink_func(child, node, new_parent);
+        }
+
+        // link to new parent
+        cx_tree_link(new_parent, child, cx_tree_node_layout(tree));
+
+        // proceed to next child
+        child = tree_next(child);
+    }
+
+    // clear the linked list of the removed node
+    tree_children(node) = NULL;
+    ptrdiff_t loc_last_child = tree->loc_last_child;
+    if (loc_last_child >= 0) tree_last_child(node) = NULL;
+
+    // the tree now has one member less
+    tree->size--;
 
     return 0;
 }
--- a/tests/test_tree.c	Sat Oct 05 14:42:14 2024 +0200
+++ b/tests/test_tree.c	Sat Oct 05 19:05:47 2024 +0200
@@ -1719,6 +1719,20 @@
     cx_testing_allocator_destroy(&talloc);
 }
 
+static void test_tree_remove_node_relink_mock(
+        void *node,
+        __attribute__((__unused__)) const void *oldp,
+        __attribute__((__unused__)) const void *newp
+) {
+    tree_node_file * n = node;
+    // this function fakes the relink logic in below test
+    if (strcmp(n->path, "/usr/share/") == 0) {
+        n->path = "/share/";
+    } else if (strcmp(n->path, "/usr/lib/") == 0) {
+        n->path = "/lib/";
+    }
+}
+
 CX_TEST(test_tree_high_add_find_remove_nodes) {
     CxTestingAllocator talloc;
     cx_testing_allocator_init(&talloc);
@@ -1765,20 +1779,41 @@
 
         tree_node_file *share = cxCalloc(alloc, 1, sizeof(tree_node_file));
         share->path = "/usr/share/";
-        cxTreeAddChildNode(tree, cxTreeFind(tree, "/usr/"), share);
+        tree_node_file *usr = cxTreeFind(tree, "/usr/");
+        cxTreeAddChildNode(tree, usr, share);
         CX_TEST_ASSERT(tree->size == 8);
 
         cxTreeRemoveSubtree(tree, foo);
+        CX_TEST_ASSERT(tree->size == 6);
+        CX_TEST_ASSERT(foo->children == bar);
+        CX_TEST_ASSERT(foo->last_child == bar);
         CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/home/foo/"));
         CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/home/foo/bar/"));
         CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/home/bar/"));
-        CX_TEST_ASSERT(tree->size == 6);
+
+        CX_TEST_ASSERT(0 == cxTreeRemove(tree, usr, test_tree_remove_node_relink_mock));
+        CX_TEST_ASSERT(tree->size == 5);
+        CX_TEST_ASSERT(usr->parent == NULL);
+        CX_TEST_ASSERT(usr->children == NULL);
+        CX_TEST_ASSERT(usr->last_child == NULL);
+        CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/usr/"));
+        CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/usr/lib/"));
+        CX_TEST_ASSERT(NULL == cxTreeFind(tree, "/usr/share/"));
+        tree_node_file *relinked_share = cxTreeFind(tree, "/share/");
+        tree_node_file *relinked_lib = cxTreeFind(tree, "/lib/");
+        CX_TEST_ASSERT(relinked_share != NULL);
+        CX_TEST_ASSERT(relinked_share->parent == tree->root);
+        CX_TEST_ASSERT(relinked_lib != NULL);
+        CX_TEST_ASSERT(relinked_lib->parent == tree->root);
+        CX_TEST_ASSERT(NULL != cxTreeFind(tree, "/home/"));
+
 
         cxTreeDestroy(tree);
-        // we are not done yet, because we need to free the removed subtree
+        // we are not done yet, because we need to free the removed stuff
         CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
 
-        // for this, we use a little trick and wrap the removed subtree
+        cxFree(alloc, usr);
+        // for the subtree, we use a little trick and wrap it in a new tree
         CxTree *foo_tree = cxTreeCreateWrapped(alloc, foo, tree_node_file_layout);
         foo_tree->allocator = alloc;
         foo_tree->advanced_destructor = (cx_destructor_func2 ) cxFree;
@@ -1811,6 +1846,9 @@
         };
         cxTreeInsertArray(tree, paths, sizeof(const char*), 6);
         void *root = tree->root;
+        CX_TEST_ASSERT(0 != cxTreeRemove(tree, root, NULL));
+        CX_TEST_ASSERT(tree->root == root);
+        CX_TEST_ASSERT(tree->size == 6);
         cxTreeRemoveSubtree(tree, root);
         CX_TEST_ASSERT(tree->size == 0);
         CX_TEST_ASSERT(tree->root == NULL);

mercurial