/* gcal-range-tree.c * * Copyright (C) 2016-2020 Georges Basile Stavracas Neto * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define G_LOG_DOMAIN "GcalRangeTree" #include "gcal-range-tree.h" #include "utils/gcal-date-time-utils.h" /** * SECTION:gcal-range-tree * @short_description: Augmented AVL tree to handle ranges * @title:GcalRangeTree * @stability:stable * * #GcalRangeTree is an augmented AVL tree implemented to be * able to handle time ranges rather than point values. This * tree is used by @GcalWeekGrid to store events according to * their start and end times. * * By using #GDateTime to store the ranges, it supports a very * long time range. * * # Ranges * * Conforming to the overall standard of iCal and Calendar, * the start of a range is inclusive but the end of the range * is always exclusive. */ typedef struct _Node { struct _Node *left; struct _Node *right; GcalRange *range; GDateTime *max; guint16 hits; gint64 height; GPtrArray *data_array; } Node; struct _GcalRangeTree { guint ref_count; GDestroyNotify destroy_func; Node *root; }; G_DEFINE_BOXED_TYPE (GcalRangeTree, gcal_range_tree, gcal_range_tree_ref, gcal_range_tree_unref) /* Auxiliary methods */ static void destroy_tree (Node *n) { if (!n) return; destroy_tree (n->left); destroy_tree (n->right); g_clear_pointer (&n->range, gcal_range_unref); gcal_clear_date_time (&n->max); g_ptr_array_unref (n->data_array); g_free (n); } static inline gint32 height (Node *n) { return n ? n->height : 0; } static inline void update_height (Node *n) { n->height = MAX (height (n->left), height (n->right)) + 1; } static inline guint32 balance (Node *n) { return n ? height (n->left) - height (n->right) : 0; } static Node* node_new (GcalRange *range, gpointer data, GDestroyNotify destroy_func) { Node *n; n = g_new0 (Node, 1); n->range = gcal_range_ref (range); n->max = gcal_range_get_end (range); n->height = 1; n->hits = 1; n->data_array = g_ptr_array_new_with_free_func (destroy_func); g_ptr_array_add (n->data_array, data); return n; } /* AVL rotations */ static Node* rotate_left (Node *n) { Node *tmp; tmp = n->right; n->right = tmp->left; tmp->left = n; /* Update heights */ update_height (n); update_height (tmp); return tmp; } static Node* rotate_right (Node *n) { Node *tmp; tmp = n->left; n->left = tmp->right; tmp->right = n; /* Update heights */ update_height (n); update_height (tmp); return tmp; } static inline Node* hit_node (Node *n, gpointer data) { n->hits++; g_ptr_array_add (n->data_array, data); return n; } static inline Node* rebalance (Node *n) { gint32 node_balance; update_height (n); /* Rotate the tree */ node_balance = balance (n); if (node_balance > 1) { if (n->left && height (n->left->right) > height (n->left->left)) n->left = rotate_left (n->left); return rotate_right (n); } else if (node_balance < -1) { if (n->right && height (n->right->right) < height (n->right->left)) n->right = rotate_right (n->right); return rotate_left (n); } return n; } static Node* insert (Node *n, GcalRange *range, gpointer data, GDestroyNotify destroy_func) { g_autoptr (GDateTime) range_end = NULL; gint result; if (!n) return node_new (range, data, destroy_func); result = gcal_range_compare (range, n->range); if (result < 0) n->left = insert (n->left, range, data, destroy_func); else if (result > 0) n->right = insert (n->right, range, data, destroy_func); else return hit_node (n, data); range_end = gcal_range_get_end (range); /* Update the current node's maximum subrange value */ if (!n->max || g_date_time_compare (range_end, n->max) > 0) gcal_set_date_time (&n->max, range_end); return rebalance (n); } /* Remove */ static Node* find_minimum (Node *n) { if (n->left) return find_minimum (n->left); return n; } static Node* delete_minimum (Node *n) { if (!n->left) return n->right; n->left = delete_minimum (n->left); return rebalance (n); } static inline Node* delete_node (Node *n, gpointer data) { Node *left, *right, *min; n->hits--; g_ptr_array_remove (n->data_array, data); /* Only remove the node when the hit count reaches zero */ if (n->hits > 0) return n; left = n->left; right = n->right; g_ptr_array_unref (n->data_array); g_free (n); if (!right) return left; min = find_minimum (right); min->right = delete_minimum (right); min->left = left; return rebalance (min); } static Node* remove_node (Node *n, GcalRange *range, gpointer data) { GcalRangePosition position; if (!n) return NULL; gcal_range_calculate_overlap (range, n->range, &position); switch (position) { case GCAL_RANGE_BEFORE: n->left = remove_node (n->left, range, data); break; case GCAL_RANGE_MATCH: return delete_node (n, data); case GCAL_RANGE_AFTER: n->right = remove_node (n->right, range, data); break; default: g_assert_not_reached (); } return rebalance (n); } /* Traverse */ static inline gboolean run_traverse_func (Node *n, GcalRangeTraverseFunc func, gpointer user_data) { guint i; for (i = 0; i < n->hits; i++) { if (func (n->range, g_ptr_array_index (n->data_array, i), user_data)) return TRUE; } return FALSE; } static gboolean traverse (Node *n, GTraverseType type, GcalRangeTraverseFunc func, gpointer user_data) { if (!n) return GCAL_TRAVERSE_CONTINUE; if (type == G_PRE_ORDER) { if (run_traverse_func (n, func, user_data)) return GCAL_TRAVERSE_STOP; } if (traverse (n->left, type, func, user_data)) return GCAL_TRAVERSE_STOP; if (type == G_IN_ORDER) { if (run_traverse_func (n, func, user_data)) return GCAL_TRAVERSE_STOP; } if (traverse (n->right, type, func, user_data)) return GCAL_TRAVERSE_STOP; if (type == G_POST_ORDER) { if (run_traverse_func (n, func, user_data)) return GCAL_TRAVERSE_STOP; } return GCAL_TRAVERSE_CONTINUE; } /* Internal traverse functions */ static inline gboolean gather_all_data (GcalRange *range, gpointer data, gpointer user_data) { GPtrArray *array = user_data; g_ptr_array_add (array, data); return FALSE; } static inline gboolean gather_data_at_range (GcalRange *range, gpointer data, gpointer user_data) { GcalRangePosition position; GcalRangeOverlap overlap; struct { GcalRange *range; GPtrArray **array; } *gather_data = user_data; overlap = gcal_range_calculate_overlap (range, gather_data->range, &position); if (overlap == GCAL_RANGE_NO_OVERLAP) { if (position == GCAL_RANGE_BEFORE) return GCAL_TRAVERSE_CONTINUE; if (position == GCAL_RANGE_AFTER) return GCAL_TRAVERSE_STOP; } if (!*gather_data->array) *gather_data->array = g_ptr_array_new (); g_ptr_array_add (*gather_data->array, data); return GCAL_TRAVERSE_CONTINUE; } static inline gboolean count_entries_at_range (GcalRange *range, gpointer data, gpointer user_data) { GcalRangePosition position; GcalRangeOverlap overlap; struct { GcalRange *range; guint64 counter; } *gather_data = user_data; overlap = gcal_range_calculate_overlap (range, gather_data->range, &position); if (overlap == GCAL_RANGE_NO_OVERLAP) { if (position == GCAL_RANGE_BEFORE) return GCAL_TRAVERSE_CONTINUE; if (position == GCAL_RANGE_AFTER) return GCAL_TRAVERSE_STOP; } gather_data->counter++; return GCAL_TRAVERSE_CONTINUE; } static inline gboolean remove_data_func (GcalRange *range, gpointer data, gpointer user_data) { struct { GcalRangeTree *range_tree; gpointer *data; } *remove_data = user_data; if (remove_data->data == data) { gcal_range_tree_remove_range (remove_data->range_tree, range, data); return GCAL_TRAVERSE_STOP; } return GCAL_TRAVERSE_CONTINUE; } static void recursively_print_node_to_string (Node *n, GString *string, gint depth) { g_autofree gchar *range = NULL; gint64 i; for (i = 0; i < depth * 2; i++) g_string_append (string, " "); if (!n) { g_string_append (string, "(null)\n"); return; } range = gcal_range_to_string (n->range); g_string_append_printf (string, "Node %s (hits: %d)\n", range, n->hits); recursively_print_node_to_string (n->left, string, depth + 1); recursively_print_node_to_string (n->right, string, depth + 1); } static void gcal_range_tree_free (GcalRangeTree *self) { g_assert (self); g_assert_cmpint (self->ref_count, ==, 0); destroy_tree (self->root); g_slice_free (GcalRangeTree, self); } /** * gcal_range_tree_new: * * Creates a new range tree * * Returns: (transfer full): a newly created #GcalRangeTree. * Free with gcal_range_tree_unref() when done. */ GcalRangeTree* gcal_range_tree_new (void) { GcalRangeTree *self; self = g_slice_new0 (GcalRangeTree); self->ref_count = 1; return self; } /** * gcal_range_tree_new_with_free_func: * @destroy_func: (nullable): a function to free elements with * * Creates a new range tree with @destroy_func as the function to * destroy elements when removing them. * * Returns: (transfer full): a newly created #GcalRangeTree. * Free with gcal_range_tree_unref() when done. */ GcalRangeTree* gcal_range_tree_new_with_free_func (GDestroyNotify destroy_func) { GcalRangeTree *self; self = g_slice_new0 (GcalRangeTree); self->ref_count = 1; self->destroy_func = destroy_func; return self; } /** * gcal_range_tree_copy: * @self: a #GcalRangeTree * * Copies @self into a new range tree. * * Returns: (transfer full): a newly created #GcalRangeTree. * Free with gcal_range_tree_unref() when done. */ GcalRangeTree* gcal_range_tree_copy (GcalRangeTree *self) { GcalRangeTree *copy; g_return_val_if_fail (self, NULL); g_return_val_if_fail (self->ref_count, NULL); copy = gcal_range_tree_new (); return copy; } /** * gcal_range_tree_ref: * @self: a #GcalRangeTree * * Increases the reference count of @self. * * Returns: (transfer full): pointer to the just-referenced tree. */ GcalRangeTree* gcal_range_tree_ref (GcalRangeTree *self) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (self->ref_count, NULL); g_atomic_int_inc (&self->ref_count); return self; } /** * gcal_range_tree_unref: * @self: a #GcalRangeTree * * Decreases the reference count of @self, and frees it when * if the reference count reaches zero. */ void gcal_range_tree_unref (GcalRangeTree *self) { g_return_if_fail (self); g_return_if_fail (self->ref_count); if (g_atomic_int_dec_and_test (&self->ref_count)) gcal_range_tree_free (self); } /** * gcal_range_tree_add_range: * @self: a #GcalRangeTree * @range: a #GcalRange * @data: user data * * Adds the range into @self, and attach @data do it. It is * possible to have multiple entries in the same interval, * in which case the interval will have a reference counting like * behavior. */ void gcal_range_tree_add_range (GcalRangeTree *self, GcalRange *range, gpointer data) { g_return_if_fail (self); g_return_if_fail (range); self->root = insert (self->root, range, data, self->destroy_func); } /** * gcal_range_tree_remove_range: * @self: a #GcalRangeTree * @range: a #GcalRange * @data: user data * * Removes (or decreases the reference count) of the given interval. */ void gcal_range_tree_remove_range (GcalRangeTree *self, GcalRange *range, gpointer data) { g_return_if_fail (self); g_return_if_fail (range); self->root = remove_node (self->root, range, data); } /** * gcal_range_tree_remove_data: * @self: a #GcalRangeTree * @data: user data * * Removes the first instance of @data found in the range tree. */ void gcal_range_tree_remove_data (GcalRangeTree *self, gpointer data) { struct { GcalRangeTree *range_tree; gpointer *data; } remove_data = { self, data }; g_return_if_fail (self); gcal_range_tree_traverse (self, G_IN_ORDER, remove_data_func, &remove_data); } /** * gcal_range_tree_traverse: * @self: a #GcalRangeTree * @type: the type of traverse to perform * @func: the function to call on each traverse iteration * @user_data: user data for @func * * Traverse @self calling @func according to the @type specified. */ void gcal_range_tree_traverse (GcalRangeTree *self, GTraverseType type, GcalRangeTraverseFunc func, gpointer user_data) { g_return_if_fail (self); traverse (self->root, type, func, user_data); } /** * gcal_range_tree_get_all_data: * @self: a #GcalRangeTree * * Retrieves all the data currently stored in @self. * * Returns: (transfer full): a #GPtrArray with the stored elements */ GPtrArray* gcal_range_tree_get_all_data (GcalRangeTree *self) { g_autoptr (GPtrArray) data = NULL; g_return_val_if_fail (self, NULL); data = g_ptr_array_new (); gcal_range_tree_traverse (self, G_IN_ORDER, gather_all_data, data); return g_steal_pointer (&data); } /** * gcal_range_tree_get_data_at_range: * @self: a #GcalRangeTree * @range: a #GcalRange * * Retrieves the data of every node within @range. * * Returns: (transfer full): a #GPtrArray. Unref with g_ptr_array_unref() * when finished. */ GPtrArray* gcal_range_tree_get_data_at_range (GcalRangeTree *self, GcalRange *range) { GPtrArray *data; struct { GcalRange *range; GPtrArray **array; } gather_data = { range, &data }; g_return_val_if_fail (self, NULL); g_return_val_if_fail (range, NULL); data = NULL; gcal_range_tree_traverse (self, G_IN_ORDER, gather_data_at_range, &gather_data); return data; } /** * gcal_range_tree_count_entries_at_range: * @self: a #GcalRangeTree * @range: a #GcalRange * * Counts the number of entries available in the given * range. * * Returns: the number of entries in the given range */ guint64 gcal_range_tree_count_entries_at_range (GcalRangeTree *self, GcalRange *range) { struct { GcalRange *range; guint64 counter; } gather_data = { range, 0 }; g_return_val_if_fail (self, 0); g_return_val_if_fail (range, 0); gcal_range_tree_traverse (self, G_IN_ORDER, count_entries_at_range, &gather_data); return gather_data.counter; } /** * gcal_range_tree_print: * @self: a #GcalRangeTree * * Prints @self. This is only useful for debugging purposes. There * are no guarantees about the format, except that it's human-readable. */ void gcal_range_tree_print (GcalRangeTree *self) { g_autoptr (GString) string = NULL; g_return_if_fail (self); string = g_string_new (""); recursively_print_node_to_string (self->root, string, 0); g_print ("%s", string->str); }