diff --git a/Makefile b/Makefile index 09b32bd..67d5d83 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,7 @@ SRC = \ gutil_ring.c \ gutil_strv.c \ gutil_timenotify.c \ + gutil_objv.c \ gutil_version.c \ gutil_weakref.c diff --git a/include/gutil_objv.h b/include/gutil_objv.h new file mode 100644 index 0000000..7ee5c90 --- /dev/null +++ b/include/gutil_objv.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GUTIL_OBJV_H +#define GUTIL_OBJV_H + +#include "gutil_types.h" + +#include + +/* + * Operations on NULL-terminated array of references to GObjects. + * + * Since 1.0.70 + */ + +G_BEGIN_DECLS + +void +gutil_objv_free( + GObject** objv); + +GObject** +gutil_objv_copy( + GObject* const* objv) + G_GNUC_WARN_UNUSED_RESULT; + +GObject** +gutil_objv_add( + GObject** objv, + GObject* obj) + G_GNUC_WARN_UNUSED_RESULT; + +GObject** +gutil_objv_remove( + GObject** objv, + GObject* obj, + gboolean all) + G_GNUC_WARN_UNUSED_RESULT; + +GObject** +gutil_objv_remove_at( + GObject** objv, + gsize pos) + G_GNUC_WARN_UNUSED_RESULT; + +GObject* +gutil_objv_at( + GObject* const* objv, + gsize pos); + +gboolean +gutil_objv_equal( + GObject* const* objv1, + GObject* const* objv2); + +GObject* +gutil_objv_first( + GObject* const* objv); + +GObject* +gutil_objv_last( + GObject* const* objv); + +gssize +gutil_objv_find( + GObject* const* objv, + GObject* obj); + +gssize +gutil_objv_find_last( + GObject* const* objv, + GObject* obj); + +gboolean +gutil_objv_contains( + GObject* const* objv, + GObject* obj); + +G_END_DECLS + +#endif /* GUTIL_OBJV_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/gutil_objv.c b/src/gutil_objv.c new file mode 100644 index 0000000..cac4692 --- /dev/null +++ b/src/gutil_objv.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2023 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gutil_objv.h" +#include "gutil_misc.h" + +void +gutil_objv_free( + GObject** objv) +{ + if (objv) { + GObject** ptr = objv; + while (*ptr) g_object_unref(*ptr++); + g_free(objv); + } +} + +GObject** +gutil_objv_copy( + GObject* const* objv) +{ + if (objv) { + GObject* const* ptr = objv; + gsize n = 0; + /* Count the services and bump references at the same time */ + while (*ptr) { + g_object_ref(*ptr++); + n++; + } + return gutil_memdup(objv, sizeof(GObject*) * (n + 1)); + } + return NULL; + +} + +GObject** +gutil_objv_add( + GObject** objv, + GObject* obj) +{ + if (obj) { + gsize len = gutil_ptrv_length(objv); + + objv = g_renew(GObject*, objv, len + 2); + g_object_ref(objv[len++] = obj); + objv[len] = NULL; + } + return objv; +} + +static +gssize +gutil_objv_find_last_impl( + GObject* const* objv, + GObject* obj, + gsize i /* exclisive */) +{ + while (i > 0) { + if (objv[--i] == obj) { + return i; + } + } + return -1; +} + +static +GObject** +gutil_objv_remove_impl( + GObject** objv, + gsize pos, + gsize len) +{ + g_object_unref(objv[pos]); + memmove(objv + pos, objv + pos + 1, sizeof(GObject*) * (len - pos)); + return g_realloc(objv, sizeof(GObject*) * len); +} + +GObject** +gutil_objv_remove( + GObject** objv, + GObject* obj, + gboolean all) +{ + if (objv && obj) { + const gssize pos = gutil_objv_find(objv, obj); + + if (pos >= 0) { + gsize len = gutil_ptrv_length(objv); + + objv = gutil_objv_remove_impl(objv, pos, len); + if (all) { + gssize i, l; + + len--; + l = len - pos; + while ((i = gutil_objv_find_last_impl(objv + pos, + obj, l)) >= 0) { + objv = gutil_objv_remove_impl(objv, pos + i, len--); + l = i; + } + } + } + } + return objv; +} + +GObject** +gutil_objv_remove_at( + GObject** objv, + gsize pos) +{ + if (objv) { + const gsize len = gutil_ptrv_length(objv); + + if (pos < len) { + objv = gutil_objv_remove_impl(objv, pos, len); + } + } + return objv; +} + +GObject* +gutil_objv_at( + GObject* const* objv, + gsize pos) +{ + if (objv) { + guint i = 0; + + while (objv[i] && i < pos) i++; + if (i == pos) { + /* We also end up here if i == len but that's OK */ + return objv[pos]; + } + } + return NULL; + +} + +gboolean +gutil_objv_equal( + GObject* const* v1, + GObject* const* v2) +{ + if (v1 == v2) { + return TRUE; + } else if (!v1) { + return !v2[0]; + } else if (!v2) { + return !v1[0]; + } else { + gsize len = 0; + + while (v1[len] && v1[len] == v2[len]) len++; + return !v1[len] && !v2[len]; + } +} + +GObject* +gutil_objv_first( + GObject* const* objv) +{ + return objv ? objv[0] : NULL; +} + +GObject* +gutil_objv_last( + GObject* const* objv) +{ + if (objv && objv[0]) { + GObject* const* ptr = objv; + + while (ptr[1]) ptr++; + return *ptr; + } + return NULL; +} + +gssize +gutil_objv_find( + GObject* const* objv, + GObject* obj) +{ + if (objv && obj) { + GObject* const* ptr; + + for (ptr = objv; *ptr; ptr++) { + if (*ptr == obj) { + return ptr - objv; + } + } + } + return -1; +} + +gssize +gutil_objv_find_last( + GObject* const* objv, + GObject* obj) +{ + return (objv && obj) ? + gutil_objv_find_last_impl(objv, obj, gutil_ptrv_length(objv)) : + -1; +} + +gboolean +gutil_objv_contains( + GObject* const* objv, + GObject* obj) +{ + if (objv && obj) { + GObject* const* ptr; + + for (ptr = objv; *ptr; ptr++) { + if (*ptr == obj) { + return TRUE; + } + } + } + return FALSE; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/test/Makefile b/test/Makefile index 7d76912..97909f9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -11,6 +11,7 @@ all: @$(MAKE) -C test_ints $* @$(MAKE) -C test_log $* @$(MAKE) -C test_misc $* + @$(MAKE) -C test_objv $* @$(MAKE) -C test_ring $* @$(MAKE) -C test_strv $* @$(MAKE) -C test_weakref $* diff --git a/test/coverage/run b/test/coverage/run index fb3d7b1..a982970 100755 --- a/test/coverage/run +++ b/test/coverage/run @@ -13,6 +13,7 @@ test_intarray \ test_ints \ test_log \ test_misc \ +test_objv \ test_ring \ test_strv \ test_weakref" diff --git a/test/test_objv/Makefile b/test/test_objv/Makefile new file mode 100644 index 0000000..7561dcf --- /dev/null +++ b/test/test_objv/Makefile @@ -0,0 +1,5 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_objv + +include ../common/Makefile diff --git a/test/test_objv/test_objv.c b/test/test_objv/test_objv.c new file mode 100644 index 0000000..d86c4a4 --- /dev/null +++ b/test/test_objv/test_objv.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2023 Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_common.h" + +#include "gutil_objv.h" +#include "gutil_misc.h" + +static TestOpt test_opt; + +/*==========================================================================* + * null + *==========================================================================*/ + +static +void +test_null( + void) +{ + g_assert(!gutil_objv_copy(NULL)); + g_assert(!gutil_objv_add(NULL, NULL)); + g_assert(!gutil_objv_remove(NULL, NULL, FALSE)); + g_assert(!gutil_objv_remove_at(NULL, 0)); + g_assert(!gutil_objv_at(NULL, 0)); + g_assert(!gutil_objv_first(NULL)); + g_assert(!gutil_objv_last(NULL)); + g_assert(!gutil_objv_contains(NULL, NULL)); + g_assert_cmpint(gutil_objv_find(NULL, NULL), < ,0); + g_assert_cmpint(gutil_objv_find_last(NULL, NULL), < ,0); + g_assert(gutil_objv_equal(NULL, NULL)); + gutil_objv_free(NULL); +} + +/*==========================================================================* + * basic + *==========================================================================*/ + +static +void +test_basic( + void) +{ + GObject* o1 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject* o2 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject** v = gutil_objv_add(NULL, o1); + GWeakRef r1, r2; + + g_weak_ref_init(&r1, o1); + g_weak_ref_init(&r2, o2); + + /* v keeps references to both objects */ + g_object_unref(o1); + g_assert(g_weak_ref_get(&r1) == o1); + g_object_unref(o1); + + g_assert(gutil_objv_contains(v, o1)); + g_assert(!gutil_objv_contains(v, o2)); + + g_assert_cmpuint(gutil_ptrv_length(v), == ,1); + v = gutil_objv_add(v, o2); + g_assert_cmpuint(gutil_ptrv_length(v), == ,2); + g_assert(gutil_objv_contains(v, o2)); + + g_assert(gutil_objv_at(v, 0) == o1); + g_assert(gutil_objv_at(v, 1) == o2); + g_assert(!gutil_objv_at(v, 2)); + g_assert(!gutil_objv_at(v, 3)); + + g_assert(gutil_objv_first(v) == o1); + g_assert(gutil_objv_last(v) == o2); + g_assert_cmpint(gutil_objv_find(v, o1), == ,0); + g_assert_cmpint(gutil_objv_find_last(v, o1), == ,0); + + v = gutil_objv_remove(v, o1, FALSE); + g_assert_cmpuint(gutil_ptrv_length(v), == ,1); + g_assert(!gutil_objv_at(v, 1)); + g_assert(gutil_objv_remove(v, o1, FALSE) == v); + g_assert(gutil_objv_remove(v, NULL, FALSE) == v); + g_assert_cmpuint(gutil_ptrv_length(v), == ,1); + g_assert(!g_weak_ref_get(&r1)); + + g_object_unref(o2); + gutil_objv_free(v); + g_assert(!g_weak_ref_get(&r2)); +} + +/*==========================================================================* + * copy + *==========================================================================*/ + +static +void +test_copy( + void) +{ + GObject* o1 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject* o2 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject** v1; + GObject** v2; + GWeakRef r1, r2; + + g_weak_ref_init(&r1, o1); + g_weak_ref_init(&r2, o2); + + v1 = gutil_objv_add(gutil_objv_add(NULL, o1), o2); + v2 = gutil_objv_copy(v1); + + /* Don't need these references anymore */ + g_object_unref(o1); + g_object_unref(o2); + + g_assert_cmpuint(gutil_ptrv_length(v1), == ,2); + g_assert_cmpuint(gutil_ptrv_length(v2), == ,2); + g_assert(gutil_objv_equal(v1, v2)); + g_assert(gutil_objv_equal(v2, v1)); + g_assert(gutil_objv_equal(v1, v1)); + + v1 = gutil_objv_remove_at(v1, 1); + g_assert(!gutil_objv_equal(v1, v2)); + g_assert(!gutil_objv_equal(v2, v1)); + g_assert(!gutil_objv_equal(v1, NULL)); + g_assert(!gutil_objv_equal(NULL, v1)); + + v2 = gutil_objv_remove_at(v2, 0); + g_assert(!gutil_objv_equal(v1, v2)); + g_assert(!gutil_objv_equal(v2, v1)); + + v1 = gutil_objv_remove_at(v1, 0); + g_assert(gutil_objv_remove_at(v1, 0) == v1); + g_assert(gutil_objv_equal(v1, NULL)); + g_assert(gutil_objv_equal(NULL, v1)); + g_assert(!gutil_objv_first(v1)); + g_assert(!gutil_objv_last(v1)); + + g_assert_cmpint(gutil_objv_find(v1, NULL), < ,0); + g_assert_cmpint(gutil_objv_find_last(v1, NULL), < ,0); + g_assert_cmpint(gutil_objv_find(v1, o1), < ,0); + g_assert_cmpint(gutil_objv_find_last(v1, o1), < ,0); + g_assert(!gutil_objv_contains(v1, NULL)); + g_assert(!gutil_objv_contains(v1, o1)); + + gutil_objv_free(v1); + gutil_objv_free(v2); + + g_assert(!g_weak_ref_get(&r1)); + g_assert(!g_weak_ref_get(&r2)); +} + +/*==========================================================================* + * remove + *==========================================================================*/ + +static +void +test_remove( + void) +{ + GObject* o1 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject* o2 = g_object_new(TEST_OBJECT_TYPE, NULL); + GObject** v; + GWeakRef r1, r2; + + g_weak_ref_init(&r1, o1); + g_weak_ref_init(&r2, o2); + v = gutil_objv_add(gutil_objv_add(gutil_objv_add(NULL, o1), o2), o1); + g_assert_cmpint(gutil_objv_find(v, o1), == ,0); + g_assert_cmpint(gutil_objv_find_last(v, o1), == ,2); + v = gutil_objv_remove(v, o1, TRUE); + g_assert_cmpuint(gutil_ptrv_length(v), == ,1); + g_assert(!gutil_objv_contains(v, o1)); + gutil_objv_free(v); + + g_object_unref(o1); + g_object_unref(o2); + + g_assert(!g_weak_ref_get(&r1)); + g_assert(!g_weak_ref_get(&r2)); +} + +/*==========================================================================* + * Common + *==========================================================================*/ + +#define TEST_(t) "/objv/" t + +int main(int argc, char* argv[]) +{ + g_type_init(); + g_test_init(&argc, &argv, NULL); + g_test_add_func(TEST_("null"), test_null); + g_test_add_func(TEST_("basic"), test_basic); + g_test_add_func(TEST_("copy"), test_copy); + g_test_add_func(TEST_("remove"), test_remove); + test_init(&test_opt, argc, argv); + return g_test_run(); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */