1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Copyright (C) 2015 Red Hat, Inc.
21 #include "nm-default.h"
23 #include "nm-multi-index.h"
28 NMMultiIndexFuncEqual equal_fcn;
29 NMMultiIndexFuncClone clone_fcn;
34 /* when storing the first item for a multi-index id, we don't yet create
35 * the hashtable @index. Instead we store it inplace to @value0. Note that
36 * &values_data->value0 is a NULL terminated array with one item that is
37 * suitable to be returned directly from nm_multi_index_lookup(). */
45 /******************************************************************************************/
48 _values_data_destroy (ValuesData *values_data)
50 if (values_data->index) {
51 g_free (values_data->values);
52 g_hash_table_unref (values_data->index);
54 g_slice_free (ValuesData, values_data);
58 _values_data_contains (ValuesData *values_data, gconstpointer value)
60 return values_data->index
61 ? g_hash_table_contains (values_data->index, value)
62 : value == values_data->value0;
66 _values_data_get_data (ValuesData *values_data,
67 void *const**out_data,
74 nm_assert (values_data);
76 if (!values_data->index) {
77 NM_SET_OUT (out_data, &values_data->value0);
78 NM_SET_OUT (out_len, 1);
82 nm_assert (values_data->index && g_hash_table_size (values_data->index) > 0);
84 if (!values_data->values) {
85 len = g_hash_table_size (values_data->index);
86 values = g_new (gpointer, len + 1);
88 g_hash_table_iter_init (&iter, values_data->index);
89 for (i = 0; g_hash_table_iter_next (&iter, &values[i], NULL); i++)
94 values_data->values = values;
95 NM_SET_OUT (out_len, len);
97 NM_SET_OUT (out_len, g_hash_table_size (values_data->index));
99 NM_SET_OUT (out_data, values_data->values);
102 /******************************************************************************************/
105 * nm_multi_index_lookup():
108 * @out_len: (allow-none): output the number of values
111 * Returns: (transfer none): %NULL if there are no values
112 * or a %NULL terminated array of pointers.
115 nm_multi_index_lookup (const NMMultiIndex *index,
116 const NMMultiIndexId *id,
119 ValuesData *values_data;
122 g_return_val_if_fail (index, NULL);
123 g_return_val_if_fail (id, NULL);
125 values_data = g_hash_table_lookup (index->hash, id);
131 _values_data_get_data (values_data, &values, out_len);
136 nm_multi_index_contains (const NMMultiIndex *index,
137 const NMMultiIndexId *id,
140 ValuesData *values_data;
142 g_return_val_if_fail (index, FALSE);
143 g_return_val_if_fail (id, FALSE);
144 g_return_val_if_fail (value, FALSE);
146 values_data = g_hash_table_lookup (index->hash, id);
147 return values_data && _values_data_contains (values_data, value);
150 const NMMultiIndexId *
151 nm_multi_index_lookup_first_by_value (const NMMultiIndex *index,
155 const NMMultiIndexId *id;
156 ValuesData *values_data;
158 g_return_val_if_fail (index, NULL);
159 g_return_val_if_fail (value, NULL);
161 /* reverse-lookup needs to iterate over all hash tables. It should
162 * still be fairly quick, if the number of hash tables is small.
163 * There is no O(1) reverse lookup implemented, because this access
164 * pattern is not what NMMultiIndex is here for.
165 * You are supposed to use NMMultiIndex by always knowing which @id
169 g_hash_table_iter_init (&iter, index->hash);
170 while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &values_data)) {
171 if (_values_data_contains (values_data, value))
178 nm_multi_index_foreach (const NMMultiIndex *index,
180 NMMultiIndexFuncForeach foreach_func,
184 const NMMultiIndexId *id;
185 ValuesData *values_data;
189 g_return_if_fail (index);
190 g_return_if_fail (foreach_func);
192 g_hash_table_iter_init (&iter, index->hash);
193 while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &values_data)) {
194 if (value && !_values_data_contains (values_data, value))
197 _values_data_get_data (values_data, &values, &len);
198 if (!foreach_func (id, values, len, user_data))
204 nm_multi_index_iter_init (NMMultiIndexIter *iter,
205 const NMMultiIndex *index,
208 g_return_if_fail (index);
209 g_return_if_fail (iter);
211 g_hash_table_iter_init (&iter->_iter, index->hash);
212 iter->_index = index;
213 iter->_value = value;
217 nm_multi_index_iter_next (NMMultiIndexIter *iter,
218 const NMMultiIndexId **out_id,
219 void *const**out_values,
222 const NMMultiIndexId *id;
223 ValuesData *values_data;
225 g_return_val_if_fail (iter, FALSE);
227 while (g_hash_table_iter_next (&iter->_iter, (gpointer *) &id, (gpointer *) &values_data)) {
229 || _values_data_contains (values_data, iter->_value)) {
230 if (out_values || out_len)
231 _values_data_get_data (values_data, out_values, out_len);
240 /******************************************************************************************/
243 nm_multi_index_id_iter_init (NMMultiIndexIdIter *iter,
244 const NMMultiIndex *index,
245 const NMMultiIndexId *id)
247 ValuesData *values_data;
249 g_return_if_fail (index);
250 g_return_if_fail (iter);
251 g_return_if_fail (id);
253 values_data = g_hash_table_lookup (index->hash, id);
256 else if (values_data->index) {
258 g_hash_table_iter_init (&iter->_iter, values_data->index);
261 iter->_value = values_data->value0;
266 nm_multi_index_id_iter_next (NMMultiIndexIdIter *iter,
269 g_return_val_if_fail (iter, FALSE);
271 switch (iter->_state) {
274 NM_SET_OUT (out_value, iter->_value);
277 return g_hash_table_iter_next (&iter->_iter, out_value, NULL);
282 g_return_val_if_reached (FALSE);
286 /******************************************************************************************/
289 _do_add (NMMultiIndex *index,
290 const NMMultiIndexId *id,
293 ValuesData *values_data;
295 values_data = g_hash_table_lookup (index->hash, id);
297 NMMultiIndexId *id_new;
299 /* Contrary to GHashTable, we don't take ownership of the @id that was
300 * provided to nm_multi_index_add(). Instead we clone it via @clone_fcn
303 * The reason is, that we expect in most cases that there exists
304 * already a @id so that we don't need ownership of it (or clone it).
305 * By doing this, the caller can pass a stack allocated @id or
306 * reuse the @id for other insertions.
308 id_new = index->clone_fcn (id);
310 g_return_val_if_reached (FALSE);
312 values_data = g_slice_new0 (ValuesData);
313 values_data->value0 = (gpointer) value;
315 g_hash_table_insert (index->hash, id_new, values_data);
317 if (!values_data->index) {
318 if (values_data->value0 == value)
320 values_data->index = g_hash_table_new (NULL, NULL);
321 g_hash_table_replace (values_data->index, (gpointer) value, (gpointer) value);
322 g_hash_table_replace (values_data->index, values_data->value0, values_data->value0);
323 values_data->values = NULL;
325 if (!nm_g_hash_table_replace (values_data->index, (gpointer) value, (gpointer) value))
327 g_clear_pointer (&values_data->values, g_free);
334 _do_remove (NMMultiIndex *index,
335 const NMMultiIndexId *id,
338 ValuesData *values_data;
340 values_data = g_hash_table_lookup (index->hash, id);
344 if (values_data->index) {
345 if (!g_hash_table_remove (values_data->index, value))
347 if (g_hash_table_size (values_data->index) == 0)
348 g_hash_table_remove (index->hash, id);
350 g_clear_pointer (&values_data->values, g_free);
352 if (values_data->value0 != value)
354 g_hash_table_remove (index->hash, id);
361 nm_multi_index_add (NMMultiIndex *index,
362 const NMMultiIndexId *id,
365 g_return_val_if_fail (index, FALSE);
366 g_return_val_if_fail (id, FALSE);
367 g_return_val_if_fail (value, FALSE);
369 return _do_add (index, id, value);
373 nm_multi_index_remove (NMMultiIndex *index,
374 const NMMultiIndexId *id,
377 g_return_val_if_fail (index, FALSE);
378 g_return_val_if_fail (value, FALSE);
381 g_return_val_if_reached (FALSE);
382 return _do_remove (index, id, value);
386 * nm_multi_index_move:
388 * @id_old: (allow-none): remove @value at @id_old
389 * @id_new: (allow-none): add @value under @id_new
390 * @value: the value to add
392 * Similar to a remove(), followed by an add(). The difference
393 * is, that we allow %NULL for both @id_old and @id_new.
394 * And the return value indicates whether @value was successfully
395 * removed *and* added.
397 * Returns: %TRUE, if the value was removed from @id_old and added
398 * as %id_new. %FALSE could mean, that @value was not added to @id_old
399 * before, or that that @value was already part of @id_new. */
401 nm_multi_index_move (NMMultiIndex *index,
402 const NMMultiIndexId *id_old,
403 const NMMultiIndexId *id_new,
406 g_return_val_if_fail (index, FALSE);
407 g_return_val_if_fail (value, FALSE);
409 if (!id_old && !id_new) {
410 /* nothing to do, @value was and is not in @index. */
413 /* add @value to @index with @id_new */
414 return _do_add (index, id_new, value);
415 } else if (!id_new) {
416 /* remove @value from @index with @id_old */
417 return _do_remove (index, id_old, value);
418 } else if (index->equal_fcn (id_old, id_new)) {
419 if (_do_add (index, id_new, value)) {
420 /* we would expect, that @value is already in @index,
421 * Return %FALSE, if it wasn't. */
428 did_remove = _do_remove (index, id_old, value);
429 return _do_add (index, id_new, value) && did_remove;
433 /******************************************************************************************/
436 nm_multi_index_get_num_groups (const NMMultiIndex *index)
438 g_return_val_if_fail (index, 0);
439 return g_hash_table_size (index->hash);
443 nm_multi_index_new (NMMultiIndexFuncHash hash_fcn,
444 NMMultiIndexFuncEqual equal_fcn,
445 NMMultiIndexFuncClone clone_fcn,
446 NMMultiIndexFuncDestroy destroy_fcn)
450 g_return_val_if_fail (hash_fcn, NULL);
451 g_return_val_if_fail (equal_fcn, NULL);
452 g_return_val_if_fail (clone_fcn, NULL);
453 g_return_val_if_fail (destroy_fcn, NULL);
455 index = g_new (NMMultiIndex, 1);
456 index->equal_fcn = equal_fcn;
457 index->clone_fcn = clone_fcn;
459 index->hash = g_hash_table_new_full ((GHashFunc) hash_fcn,
460 (GEqualFunc) equal_fcn,
461 (GDestroyNotify) destroy_fcn,
462 (GDestroyNotify) _values_data_destroy);
467 nm_multi_index_free (NMMultiIndex *index)
469 g_return_if_fail (index);
470 g_hash_table_unref (index->hash);