From a20cf5f374e64c6db50bf33545dc67804e90eabc Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Thu, 13 May 2021 10:09:00 +0200
Subject: Define the first steps for singleton support.

---
 plugins/pychrysalide/glibext/Makefile.am |   3 +-
 plugins/pychrysalide/glibext/module.c    |   4 +
 plugins/pychrysalide/glibext/singleton.c | 756 +++++++++++++++++++++++++++++++
 plugins/pychrysalide/glibext/singleton.h |  62 +++
 src/glibext/Makefile.am                  |   1 +
 src/glibext/singleton-int.h              |  64 +++
 src/glibext/singleton.c                  | 389 ++++++++++++++++
 src/glibext/singleton.h                  |  97 ++++
 tests/glibext/singleton.py               | 101 +++++
 9 files changed, 1476 insertions(+), 1 deletion(-)
 create mode 100644 plugins/pychrysalide/glibext/singleton.c
 create mode 100644 plugins/pychrysalide/glibext/singleton.h
 create mode 100644 src/glibext/singleton-int.h
 create mode 100644 src/glibext/singleton.c
 create mode 100644 src/glibext/singleton.h
 create mode 100644 tests/glibext/singleton.py

diff --git a/plugins/pychrysalide/glibext/Makefile.am b/plugins/pychrysalide/glibext/Makefile.am
index 179a991..cfed869 100644
--- a/plugins/pychrysalide/glibext/Makefile.am
+++ b/plugins/pychrysalide/glibext/Makefile.am
@@ -13,7 +13,8 @@ libpychrysaglibext_la_SOURCES =			\
 	linegen.h linegen.c					\
 	loadedpanel.h loadedpanel.c			\
 	module.h module.c					\
-	named.h named.c
+	named.h named.c						\
+	singleton.h singleton.c
 
 libpychrysaglibext_la_LDFLAGS = 
 
diff --git a/plugins/pychrysalide/glibext/module.c b/plugins/pychrysalide/glibext/module.c
index 176d815..634cf5c 100644
--- a/plugins/pychrysalide/glibext/module.c
+++ b/plugins/pychrysalide/glibext/module.c
@@ -38,6 +38,7 @@
 #include "linegen.h"
 #include "loadedpanel.h"
 #include "named.h"
+#include "singleton.h"
 #include "../helpers.h"
 
 
@@ -104,6 +105,8 @@ bool populate_glibext_module(void)
 
     result = true;
 
+    if (result) result = ensure_python_singleton_candidate_is_registered();
+
     if (result) result = ensure_python_binary_cursor_is_registered();
     if (result) result = ensure_python_binary_portion_is_registered();
     if (result) result = ensure_python_buffer_cache_is_registered();
@@ -116,6 +119,7 @@ bool populate_glibext_module(void)
     if (result) result = ensure_python_line_generator_is_registered();
     if (result) result = ensure_python_loaded_panel_is_registered();
     if (result) result = ensure_python_named_widget_is_registered();
+    if (result) result = ensure_python_singleton_factory_is_registered();
 
     assert(result);
 
diff --git a/plugins/pychrysalide/glibext/singleton.c b/plugins/pychrysalide/glibext/singleton.c
new file mode 100644
index 0000000..2be1105
--- /dev/null
+++ b/plugins/pychrysalide/glibext/singleton.c
@@ -0,0 +1,756 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * singleton.c - équivalent Python du fichier "glibext/singleton.c"
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "singleton.h"
+
+
+#include <pygobject.h>
+
+
+#include <glibext/singleton-int.h>
+#include <plugins/dt.h>
+
+
+#include "../access.h"
+#include "../helpers.h"
+
+
+
+/* ------------------ INTERFACE POUR CANDIDAT A UNE CENTRALISATION ------------------ */
+
+
+/* Procède à l'initialisation de l'interface de candidature. */
+static void py_singleton_candidate_interface_init(GSingletonCandidateIface *, gpointer *);
+
+/* Fournit l'empreinte d'un candidat à une centralisation. */
+static guint py_singleton_candidate___hash__wrapper(const GSingletonCandidate *);
+
+/* Détermine si deux candidats à l'unicité sont identiques. */
+static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate *, const GSingletonCandidate *);
+
+/* Fournit l'empreinte d'un candidat à une centralisation. */
+static PyObject *py_singleton_candidate_hash(PyObject *, PyObject *);
+
+/* Effectue une comparaison avec un objet 'SingletonCandidate'. */
+static PyObject *py_singleton_candidate_richcompare(PyObject *, PyObject *, int);
+
+
+
+/* ------------------------- COLLECTION D'INSTANCES UNIQUES ------------------------- */
+
+
+/* Accompagne la création d'une instance dérivée en Python. */
+static PyObject *py_singleton_factory_new(PyTypeObject *, PyObject *, PyObject *);
+
+/* Initialise une instance sur la base du dérivé de GObject. */
+static int py_singleton_factory_init(PyObject *, PyObject *, PyObject *);
+
+/* Fournit l'instance unique correspondant à un objet. */
+static PyObject *py_singleton_factory_get_instance(PyObject *, PyObject *);
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                    INTERFACE POUR CANDIDAT A UNE CENTRALISATION                    */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : iface  = interface GLib à initialiser.                       *
+*                unused = adresse non utilisée ici.                           *
+*                                                                             *
+*  Description : Procède à l'initialisation de l'interface de candidature.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void py_singleton_candidate_interface_init(GSingletonCandidateIface *iface, gpointer *unused)
+{
+#define SINGLETON_CANDIDATE_DOC                                             \
+    "The SingletonCandidate class is a required interface for types aiming" \
+    " at becoming singleton instances.\n"                                   \
+    "\n"                                                                    \
+    "The main implemantations come with types derived from"                 \
+    " pychrysalide.analysis.DataType.\n"                                    \
+    "\n"                                                                    \
+    "A typical class declaration for a new implementation looks like:\n"    \
+    "\n"                                                                    \
+    "    class NewImplem(GObject.Object, SingletonCandidate):\n"            \
+    "        ...\n"                                                         \
+    "\n"                                                                    \
+    "The following methods have to be defined for new implementations:\n"   \
+    "* pychrysalide.glibext.SingletonCandidate.__hash__();\n"               \
+    "* pychrysalide.glibext.SingletonCandidate.__eq__()."
+
+    iface->hash = py_singleton_candidate___hash__wrapper;
+    iface->is_equal = py_singleton_candidate___eq__wrapper;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                                                                             *
+*  Description : Fournit l'empreinte d'un candidat à une centralisation.      *
+*                                                                             *
+*  Retour      : Empreinte de l'élément représenté.                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static guint py_singleton_candidate___hash__wrapper(const GSingletonCandidate *candidate)
+{
+    guint result;                           /* Empreinte à retourner       */
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define SINGLETON_CANDIDATE_HASH_WRAPPER PYTHON_WRAPPER_DEF     \
+(                                                               \
+    __hash__, "$self, /",                                       \
+    METH_NOARGS,                                                \
+    "Abstract method used to produce a hash of the object.\n"   \
+    "\n"                                                        \
+    "The result must be an integer value up to 64 bits."        \
+)
+
+    result = 0;
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "__hash__"))
+    {
+        pyret = run_python_method(pyobj, "__hash__", NULL);
+
+        if (pyret != NULL)
+        {
+            if (PyLong_Check(pyret))
+                result = PyLong_AsUnsignedLongMask(pyret);
+
+            Py_DECREF(pyret);
+
+        }
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                other     = second élément à analyser.                       *
+*                                                                             *
+*  Description : Détermine si deux candidats à l'unicité sont identiques.     *
+*                                                                             *
+*  Retour      : Bilan de la comparaison.                                     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate *candidate, const GSingletonCandidate *other)
+{
+    guint result;                           /* Empreinte à retourner       */
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *args;                         /* Arguments pour l'appel      */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define SINGLETON_CANDIDATE_EQ_WRAPPER PYTHON_WRAPPER_DEF       \
+(                                                               \
+    __eq__, "$self, other, /",                                  \
+    METH_NOARGS,                                                \
+    "Abstract method used to provide the *__eq__* method for"   \
+    " rich comparison.\n"                                       \
+    "\n"                                                        \
+    "The expected result is a boolean value."                   \
+)
+
+    result = 0;
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "__eq__"))
+    {
+        args = PyTuple_New(1);
+        PyTuple_SetItem(args, 0, pygobject_new(G_OBJECT(other)));
+
+        pyret = run_python_method(pyobj, "__eq__", args);
+
+        if (pyret != NULL)
+        {
+            if (PyLong_Check(pyret))
+                result = PyLong_AsUnsignedLong(pyret);
+
+            Py_DECREF(pyret);
+
+        }
+
+        Py_DECREF(args);
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet dont l'instance se veut unique.                 *
+*                args = adresse non utilisée ici.                             *
+*                                                                             *
+*  Description : Fournit l'empreinte d'un candidat à une centralisation.      *
+*                                                                             *
+*  Retour      : Empreinte de l'élément représenté.                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_candidate_hash(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Emplacement à retourner     */
+    GSingletonCandidate *candidate;         /* Mécanismes natifs           */
+    guint hash;                             /* Valeur d'empreitne          */
+
+#define SINGLETON_CANDIDATE_HASH_METHOD PYTHON_METHOD_DEF           \
+(                                                                   \
+    hash, "$self",                                                  \
+    METH_NOARGS, py_singleton_candidate,                            \
+    "Compute the hash value of the singleton candidate.\n"          \
+    "\n"                                                            \
+    "The result is an integer value.\n"                             \
+    "\n"                                                            \
+    "Even if the Python *hash()* method, relying on the"            \
+    " pychrysalide.glibext.SingletonCandidate.__hash__()"           \
+    " implementation, provides values up to 64 bits, the final"     \
+    " hashes processed by the native GLib hash methods are"         \
+    " limited to 32 bits values."                                   \
+)
+
+    candidate = G_SINGLETON_CANDIDATE(pygobject_get(self));
+
+    hash = g_singleton_candidate_hash(candidate);
+
+    result = PyLong_FromUnsignedLong(hash);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : a  = premier object Python à consulter.                      *
+*                b  = second object Python à consulter.                       *
+*                op = type de comparaison menée.                              *
+*                                                                             *
+*  Description : Effectue une comparaison avec un objet 'SingletonCandidate'. *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_candidate_richcompare(PyObject *a, PyObject *b, int op)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    int ret;                                /* Bilan de lecture des args.  */
+    const GSingletonCandidate *cand_a;      /* Premier élément à traiter   */
+    const GSingletonCandidate *cand_b;      /* Second élément à traiter    */
+    gboolean status;                        /* Résultat d'une comparaison  */
+
+    if (op != Py_EQ)
+    {
+        result = Py_NotImplemented;
+        goto cmp_done;
+    }
+
+    ret = PyObject_IsInstance(b, (PyObject *)get_python_singleton_candidate_type());
+    if (!ret)
+    {
+        result = Py_NotImplemented;
+        goto cmp_done;
+    }
+
+    cand_a = G_SINGLETON_CANDIDATE(pygobject_get(a));
+    cand_b = G_SINGLETON_CANDIDATE(pygobject_get(b));
+
+    status = g_singleton_candidate_is_equal(cand_a, cand_b);
+
+    result = (status ? Py_True : Py_False);
+
+ cmp_done:
+
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_singleton_candidate_type(void)
+{
+    static PyMethodDef py_singleton_candidate_methods[] = {
+        SINGLETON_CANDIDATE_HASH_WRAPPER,
+        SINGLETON_CANDIDATE_EQ_WRAPPER,
+        SINGLETON_CANDIDATE_HASH_METHOD,
+        { NULL }
+    };
+
+    static PyGetSetDef py_singleton_candidate_getseters[] = {
+        { NULL }
+    };
+
+    static PyTypeObject py_singleton_candidate_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.glibext.SingletonCandidate",
+        .tp_basicsize   = sizeof(PyObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = SINGLETON_CANDIDATE_DOC,
+
+        .tp_richcompare = py_singleton_candidate_richcompare,
+
+        .tp_methods     = py_singleton_candidate_methods,
+        .tp_getset      = py_singleton_candidate_getseters
+
+    };
+
+    return &py_singleton_candidate_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide....SingletonCandidate'.*
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_singleton_candidate_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'BinContent'    */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    static GInterfaceInfo info = {          /* Paramètres d'inscription    */
+
+        .interface_init = (GInterfaceInitFunc)py_singleton_candidate_interface_init,
+        .interface_finalize = NULL,
+        .interface_data = NULL,
+
+    };
+
+    type = get_python_singleton_candidate_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        module = get_access_to_python_module("pychrysalide.glibext");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_interface_for_pygobject(dict, G_TYPE_SINGLETON_CANDIDATE, type, &info))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : arg = argument quelconque à tenter de convertir.             *
+*                dst = destination des valeurs récupérées en cas de succès.   *
+*                                                                             *
+*  Description : Tente de convertir en candidat à une centralisation.         *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_singleton_candidate(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_singleton_candidate_type());
+
+    switch (result)
+    {
+        case -1:
+            /* L'exception est déjà fixée par Python */
+            result = 0;
+            break;
+
+        case 0:
+            PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to singleton candidate");
+            break;
+
+        case 1:
+            *((GSingletonCandidate **)dst) = G_SINGLETON_CANDIDATE(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                           COLLECTION D'INSTANCES UNIQUES                           */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : type = type du nouvel objet à mettre en place.               *
+*                args = éventuelle liste d'arguments.                         *
+*                kwds = éventuel dictionnaire de valeurs mises à disposition. *
+*                                                                             *
+*  Description : Accompagne la création d'une instance dérivée en Python.     *
+*                                                                             *
+*  Retour      : Nouvel objet Python mis en place ou NULL en cas d'échec.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_factory_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyObject *result;                       /* Objet à retourner           */
+    PyTypeObject *base;                     /* Type de base à dériver      */
+    bool first_time;                        /* Evite les multiples passages*/
+    GType gtype;                            /* Nouveau type de processeur  */
+    bool status;                            /* Bilan d'un enregistrement   */
+
+    /* Validations diverses */
+
+    base = get_python_singleton_factory_type();
+
+    if (type == base)
+        goto simple_way;
+
+    /* Mise en place d'un type dédié */
+
+    first_time = (g_type_from_name(type->tp_name) == 0);
+
+    gtype = build_dynamic_type(G_TYPE_SINGLETON_FACTORY, type->tp_name, NULL, NULL, NULL);
+
+    if (first_time)
+    {
+        status = register_class_for_dynamic_pygobject(gtype, type, base);
+
+        if (!status)
+        {
+            result = NULL;
+            goto exit;
+        }
+
+    }
+
+    /* On crée, et on laisse ensuite la main à PyGObject_Type.tp_init() */
+
+ simple_way:
+
+    result = PyType_GenericNew(type, args, kwds);
+
+ exit:
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet à initialiser (théoriquement).                  *
+*                args = arguments fournis à l'appel.                          *
+*                kwds = arguments de type key=val fournis.                    *
+*                                                                             *
+*  Description : Initialise une instance sur la base du dérivé de GObject.    *
+*                                                                             *
+*  Retour      : 0.                                                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static int py_singleton_factory_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    int ret;                                /* Bilan de lecture des args.  */
+
+#define SINGLETON_FACTORY_DOC                                               \
+    "The SingletonFactory class reduces the memory footprint by compacting" \
+    " the quantity of running instances of a given GLib type.\n"            \
+    "\n"                                                                    \
+    "Instances can be created using the following constructor:\n"           \
+    "\n"                                                                    \
+    "    SingletonFactory()"                                                \
+    "\n"                                                                    \
+    "The first processed instance defines the type handled by the factory."
+
+    /* Récupération des paramètres */
+
+    ret = PyArg_ParseTuple(args, "");
+    if (!ret) return -1;
+
+    /* Initialisation d'un objet GLib */
+
+    ret = forward_pygobjet_init(self);
+    if (ret == -1) return -1;
+
+    /* Eléments de base */
+
+    return 0;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = gestionnaire d'instances uniques à consulter.         *
+*                args = arguments fournis à l'appel.                          *
+*                                                                             *
+*  Description : Fournit l'instance unique correspondant à un objet.          *
+*                                                                             *
+*  Retour      : Instance unique à utiliser.                                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_factory_get_instance(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Emplacement à retourner     */
+    GSingletonCandidate *candidate;         /* Candidat à traiter          */
+    int ret;                                /* Bilan de lecture des args.  */
+    GSingletonFactory *factory;             /* Gestionnaire à manipuler    */
+    GSingletonCandidate *instance;          /* Instance unique à retourner */
+
+#define SINGLETON_FACTORY_GET_INSTANCE_METHOD PYTHON_METHOD_DEF         \
+(                                                                       \
+    get_instance, "$self, candidate, /",                                \
+    METH_VARARGS, py_singleton_factory,                                 \
+    "Provide the unique instance for a given singleton *candidate*.\n"  \
+    "\n"                                                                \
+    "The *candidate* object and the result of the function belong to"   \
+    " the same type: pychrysalide.glibext.SingletonCandidate."          \
+)
+
+    ret = PyArg_ParseTuple(args, "O&", convert_to_singleton_candidate, &candidate);
+    if (!ret) return NULL;
+
+    factory = G_SINGLETON_FACTORY(pygobject_get(self));
+
+    instance = g_singleton_factory_get_instance(factory, candidate);
+
+    result = pygobject_new(G_OBJECT(instance));
+
+    g_object_unref(G_OBJECT(instance));
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_singleton_factory_type(void)
+{
+    static PyMethodDef py_singleton_factory_methods[] = {
+        SINGLETON_FACTORY_GET_INSTANCE_METHOD,
+        { NULL }
+    };
+
+    static PyGetSetDef py_singleton_factory_getseters[] = {
+        { NULL }
+    };
+
+    static PyTypeObject py_singleton_factory_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.glibext.SingletonFactory",
+        .tp_basicsize   = sizeof(PyGObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = SINGLETON_FACTORY_DOC,
+
+        .tp_methods     = py_singleton_factory_methods,
+        .tp_getset      = py_singleton_factory_getseters,
+
+        .tp_init        = py_singleton_factory_init,
+        .tp_new         = py_singleton_factory_new
+
+    };
+
+    return &py_singleton_factory_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide....SingletonFactory'.  *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_singleton_factory_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'ObjectSingleton' */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    type = get_python_singleton_factory_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        module = get_access_to_python_module("pychrysalide.glibext");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_class_for_pygobject(dict, G_TYPE_SINGLETON_FACTORY, type, &PyGObject_Type))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : arg = argument quelconque à tenter de convertir.             *
+*                dst = destination des valeurs récupérées en cas de succès.   *
+*                                                                             *
+*  Description : Tente de convertir en compacteur d'instances.                *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_singleton_factory(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_singleton_factory_type());
+
+    switch (result)
+    {
+        case -1:
+            /* L'exception est déjà fixée par Python */
+            result = 0;
+            break;
+
+        case 0:
+            PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to singleton factory");
+            break;
+
+        case 1:
+            *((GSingletonFactory **)dst) = G_SINGLETON_FACTORY(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/glibext/singleton.h b/plugins/pychrysalide/glibext/singleton.h
new file mode 100644
index 0000000..6479219
--- /dev/null
+++ b/plugins/pychrysalide/glibext/singleton.h
@@ -0,0 +1,62 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * singleton.h - prototypes pour l'équivalent Python du fichier "glibext/singleton.h"
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_SINGLETON_H
+#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_SINGLETON_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* ------------------ INTERFACE POUR CANDIDAT A UNE CENTRALISATION ------------------ */
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_singleton_candidate_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.glibext.SingletonCandidate'. */
+bool ensure_python_singleton_candidate_is_registered(void);
+
+/* Tente de convertir en candidat à une centralisation. */
+int convert_to_singleton_candidate(PyObject *, void *);
+
+
+
+/* ------------------------- COLLECTION D'INSTANCES UNIQUES ------------------------- */
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_singleton_factory_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.glibext.SingletonFactory'. */
+bool ensure_python_singleton_factory_is_registered(void);
+
+/* Tente de convertir en compacteur d'instances. */
+int convert_to_singleton_factory(PyObject *, void *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_SINGLETON_H */
diff --git a/src/glibext/Makefile.am b/src/glibext/Makefile.am
index 07a72d1..6bcf4b8 100644
--- a/src/glibext/Makefile.am
+++ b/src/glibext/Makefile.am
@@ -32,6 +32,7 @@ libglibext_la_SOURCES =					\
 	proto.h								\
 	seq.h seq.c							\
 	signal.h signal.c					\
+	singleton.h singleton.c				\
 	widthtracker.h widthtracker.c
 
 libglibext_la_LIBADD = 					\
diff --git a/src/glibext/singleton-int.h b/src/glibext/singleton-int.h
new file mode 100644
index 0000000..ac31a32
--- /dev/null
+++ b/src/glibext/singleton-int.h
@@ -0,0 +1,64 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * singleton-int.h - définitions internes propres aux réductions du nombre d'instances d'un même type
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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 Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _GLIBEXT_SINGLETON_INT_H
+#define _GLIBEXT_SINGLETON_INT_H
+
+
+#include "singleton.h"
+
+
+
+/* Fournit l'empreinte d'un candidat à une centralisation. */
+typedef guint (* hash_candidate_fc) (const GSingletonCandidate *);
+
+/* Détermine si deux candidats à l'unicité sont identiques. */
+typedef gboolean (* is_candidate_equal_fc) (const GSingletonCandidate *, const GSingletonCandidate *);
+
+/* Marque un candidat comme traité ou en cours de traitement. */
+typedef void (* mark_candidate_as_processed_fc) (GSingletonCandidate *, bool);
+
+/* Indique si un objet marqué comme unique. */
+typedef bool (* is_candidate_processed_fc) (const GSingletonCandidate *, bool);
+
+
+/* Instance d'objet visant à être unique (interface) */
+struct _GSingletonCandidateIface
+{
+    GTypeInterface base_iface;              /* A laisser en premier        */
+
+    hash_candidate_fc hash;                 /* Prise d'empreinte           */
+    is_candidate_equal_fc is_equal;         /* Comparaison                 */
+
+    mark_candidate_as_processed_fc mark;    /* Définition de l'état        */
+    is_candidate_processed_fc is_processed; /* Consultation de l'état      */
+
+};
+
+
+/* Redéfinition */
+typedef GSingletonCandidateIface GSingletonCandidateInterface;
+
+
+
+#endif  /* _GLIBEXT_SINGLETON_INT_H */
diff --git a/src/glibext/singleton.c b/src/glibext/singleton.c
new file mode 100644
index 0000000..f0ce86f
--- /dev/null
+++ b/src/glibext/singleton.c
@@ -0,0 +1,389 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * singleton.c - réduction du nombre d'instances d'un même type
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "singleton.h"
+
+
+#include <assert.h>
+
+
+#include "singleton-int.h"
+
+
+
+/* ------------------ INTERFACE POUR CANDIDAT A UNE CENTRALISATION ------------------ */
+
+/* Procède à l'initialisation de l'interface de rassemblement. */
+static void g_singleton_candidate_default_init(GSingletonCandidateInterface *);
+
+
+
+/* ------------------------- COLLECTION D'INSTANCES UNIQUES ------------------------- */
+
+
+/* Définition d'un compacteur d'instances de types (instance) */
+struct _GSingletonFactory
+{
+    GObject parent;                         /* A laisser en premier        */
+
+    GHashTable *table;                      /* Suivi des conservations     */
+    GMutex access;                          /* Verrou pour la concurrence  */
+
+};
+
+/* Définition d'un compacteur d'instances de types (classe) */
+struct _GSingletonFactoryClass
+{
+    GObjectClass parent;                    /* A laisser en premier        */
+
+};
+
+
+/* Initialise la classe des compacteurs d'instances de types. */
+static void g_singleton_factory_class_init(GSingletonFactoryClass *);
+
+/* Initialise une instance de compacteur d'instances de types. */
+static void g_singleton_factory_init(GSingletonFactory *);
+
+/* Supprime toutes les références externes. */
+static void g_singleton_factory_dispose(GSingletonFactory *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_singleton_factory_finalize(GSingletonFactory *);
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                    INTERFACE POUR CANDIDAT A UNE CENTRALISATION                    */
+/* ---------------------------------------------------------------------------------- */
+
+
+/* Détermine le type d'une interface pour la lecture de binaire. */
+G_DEFINE_INTERFACE(GSingletonCandidate, g_singleton_candidate, G_TYPE_OBJECT)
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : iface = interface GLib à initialiser.                        *
+*                                                                             *
+*  Description : Procède à l'initialisation de l'interface de rassemblement.  *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_candidate_default_init(GSingletonCandidateInterface *iface)
+{
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                                                                             *
+*  Description : Fournit l'empreinte d'un candidat à une centralisation.      *
+*                                                                             *
+*  Retour      : Empreinte de l'élément représenté.                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+guint g_singleton_candidate_hash(const GSingletonCandidate *candidate)
+{
+    guint result;                           /* Valeur à retourner          */
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    result = iface->hash(candidate);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                other     = second élément à analyser.                       *
+*                                                                             *
+*  Description : Détermine si deux candidats à l'unicité sont identiques.     *
+*                                                                             *
+*  Retour      : Bilan de la comparaison.                                     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+gboolean g_singleton_candidate_is_equal(const GSingletonCandidate *candidate, const GSingletonCandidate *other)
+{
+    gboolean result;                        /* Bilan à renvoyer            */
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    result = iface->is_equal(candidate, other);
+
+    return result;
+
+}
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                soon      = indique un traitement démarré et en cours.       *
+*                                                                             *
+*  Description : Marque un candidat comme traité ou en cours de traitement.   *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_singleton_candidate_mark_as_processed(GSingletonCandidate *candidate, bool soon)
+{
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    iface->mark(candidate, soon);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                soon      = indique un traitement démarré et en cours.       *
+*                                                                             *
+*  Description : Indique si un objet marqué comme unique.                     *
+*                                                                             *
+*  Retour      : true si l'objet est traité ou en phase de l'être, ou false.  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_singleton_candidate_is_processed(const GSingletonCandidate *candidate, bool soon)
+{
+    bool result;                            /* Statut à retourner          */
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    result = iface->is_processed(candidate, soon);
+
+    return result;
+
+
+}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                           COLLECTION D'INSTANCES UNIQUES                           */
+/* ---------------------------------------------------------------------------------- */
+
+
+/* Indique le type défini pour une mémoire de types d'objets. */
+G_DEFINE_TYPE(GSingletonFactory, g_singleton_factory, G_TYPE_OBJECT);
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : klass = classe à initialiser.                                *
+*                                                                             *
+*  Description : Initialise la classe des compacteurs d'instances de types.   *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_factory_class_init(GSingletonFactoryClass *klass)
+{
+    GObjectClass *object;                   /* Autre version de la classe  */
+
+    object = G_OBJECT_CLASS(klass);
+
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_singleton_factory_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_singleton_factory_finalize;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : factory = instance à initialiser.                            *
+*                                                                             *
+*  Description : Initialise une instance de compacteur d'instances de types.  *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_factory_init(GSingletonFactory *factory)
+{
+    factory->table = g_hash_table_new_full((GHashFunc)g_singleton_candidate_hash,
+                                           (GEqualFunc)g_singleton_candidate_is_equal,
+                                           g_object_unref, NULL);
+
+    g_mutex_init(&factory->access);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : factory = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Supprime toutes les références externes.                     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_factory_dispose(GSingletonFactory *factory)
+{
+    if (factory->table != NULL)
+    {
+        g_hash_table_unref(factory->table);
+        factory->table = NULL;
+    }
+
+    G_OBJECT_CLASS(g_singleton_factory_parent_class)->dispose(G_OBJECT(factory));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : factory = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Procède à la libération totale de la mémoire.                *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_factory_finalize(GSingletonFactory *factory)
+{
+    g_mutex_clear(&factory->access);
+
+    G_OBJECT_CLASS(g_singleton_factory_parent_class)->finalize(G_OBJECT(factory));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Crée un compacteur d'instances de types.                     *
+*                                                                             *
+*  Retour      : Instance mise en place.                                      *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GSingletonFactory *g_singleton_factory_new(void)
+{
+    GSingletonFactory *result;                    /* Structure à retourner       */
+
+    result = g_object_new(G_TYPE_SINGLETON_FACTORY, NULL);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : factory   = gestionnaire d'instances uniques à consulter.    *
+*                candidate = instance à conserver ou oublier.                 *
+*                                                                             *
+*  Description : Fournit l'instance unique correspondant à un objet.          *
+*                                                                             *
+*  Retour      : Instance unique à utiliser.                                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GSingletonCandidate *g_singleton_factory_get_instance(GSingletonFactory *factory, GSingletonCandidate *candidate)
+{
+    GSingletonCandidate *result;            /* Instance unique à retourner */
+#ifndef NDEBUG
+    gboolean status;                         /* Validation d'une opération */
+#endif
+
+    g_mutex_lock(&factory->access);
+
+    if (g_hash_table_contains(factory->table, candidate))
+    {
+#ifndef NDEBUG
+        status = g_hash_table_lookup_extended(factory->table, candidate, (void **)&result, NULL);
+        assert(status);
+#else
+        g_hash_table_lookup_extended(factory->table, candidate, (void **)&result, NULL);
+#endif
+
+    }
+
+    else
+    {
+        g_object_ref(G_OBJECT(candidate));
+
+#ifndef NDEBUG
+        status = g_hash_table_add(factory->table, candidate);
+        assert(status);
+#else
+        g_hash_table_add(factory->table, candidate);
+#endif
+
+        result = candidate;
+
+    }
+
+    g_object_ref(G_OBJECT(result));
+
+    g_mutex_unlock(&factory->access);
+
+    return result;
+
+}
diff --git a/src/glibext/singleton.h b/src/glibext/singleton.h
new file mode 100644
index 0000000..6de9f41
--- /dev/null
+++ b/src/glibext/singleton.h
@@ -0,0 +1,97 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * singleton.h - prototypes pour la réduction du nombre d'instances d'un même type
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _GLIBEXT_SINGLETON_H
+#define _GLIBEXT_SINGLETON_H
+
+
+#include <glib-object.h>
+#include <stdbool.h>
+
+
+
+/* ------------------ INTERFACE POUR CANDIDAT A UNE CENTRALISATION ------------------ */
+
+
+#define G_TYPE_SINGLETON_CANDIDATE             (g_singleton_candidate_get_type())
+#define G_SINGLETON_CANDIDATE(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_SINGLETON_CANDIDATE, GSingletonCandidate))
+#define G_SINGLETON_CANDIDATE_CLASS(vtable)    (G_TYPE_CHECK_CLASS_CAST((vtable), G_TYPE_SINGLETON_CANDIDATE, GSingletonCandidateIface))
+#define G_IS_SINGLETON_CANDIDATE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_SINGLETON_CANDIDATE))
+#define G_IS_SINGLETON_CANDIDATE_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE((vtable), G_TYPE_SINGLETON_CANDIDATE))
+#define G_SINGLETON_CANDIDATE_GET_IFACE(inst)  (G_TYPE_INSTANCE_GET_INTERFACE((inst), G_TYPE_SINGLETON_CANDIDATE, GSingletonCandidateIface))
+
+
+/* Instance d'objet visant à être unique (coquille vide) */
+typedef struct _GSingletonCandidate GSingletonCandidate;
+
+/* Instance d'objet visant à être unique (interface) */
+typedef struct _GSingletonCandidateIface GSingletonCandidateIface;
+
+
+/* Détermine le type d'une interface pour la lecture de binaire. */
+GType g_singleton_candidate_get_type(void) G_GNUC_CONST;
+
+/* Fournit l'empreinte d'un candidat à une centralisation. */
+guint g_singleton_candidate_hash(const GSingletonCandidate *);
+
+/* Détermine si deux candidats à l'unicité sont identiques. */
+gboolean g_singleton_candidate_is_equal(const GSingletonCandidate *, const GSingletonCandidate *);
+
+/* Marque un candidat comme traité ou en cours de traitement. */
+void g_singleton_candidate_mark_as_processed(GSingletonCandidate *, bool);
+
+/* Indique si un objet marqué comme unique. */
+bool g_singleton_candidate_is_processed(const GSingletonCandidate *, bool);
+
+
+
+/* ------------------------- COLLECTION D'INSTANCES UNIQUES ------------------------- */
+
+
+#define G_TYPE_SINGLETON_FACTORY            g_singleton_factory_get_type()
+#define G_SINGLETON_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_SINGLETON_FACTORY, GSingletonFactory))
+#define G_IS_SINGLETON_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_SINGLETON_FACTORY))
+#define G_SINGLETON_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_SINGLETON_FACTORY, GSingletonFactoryClass))
+#define G_IS_SINGLETON_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_SINGLETON_FACTORY))
+#define G_SINGLETON_FACTORY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_SINGLETON_FACTORY, GSingletonFactoryClass))
+
+
+/* Définition d'un compacteur d'instances de types (instance) */
+typedef struct _GSingletonFactory GSingletonFactory;
+
+/* Définition d'un compacteur d'instances de types (classe) */
+typedef struct _GSingletonFactoryClass GSingletonFactoryClass;
+
+
+/* Indique le type défini pour une mémoire de types d'objets. */
+GType g_singleton_factory_get_type(void);
+
+/* Crée un compacteur d'instances de types. */
+GSingletonFactory *g_singleton_factory_new(void);
+
+/* Fournit l'instance unique correspondant à un objet. */
+GSingletonCandidate *g_singleton_factory_get_instance(GSingletonFactory *, GSingletonCandidate *);
+
+
+
+#endif  /* _GLIBEXT_SINGLETON_H */
diff --git a/tests/glibext/singleton.py b/tests/glibext/singleton.py
new file mode 100644
index 0000000..b0608f0
--- /dev/null
+++ b/tests/glibext/singleton.py
@@ -0,0 +1,101 @@
+
+from chrysacase import ChrysalideTestCase
+from gi.repository import GObject
+from pychrysalide.glibext import SingletonCandidate, SingletonFactory
+
+
+class TestSingleton(ChrysalideTestCase):
+    """Test cases for pychrysalide.glibext.SingletonFactory."""
+
+
+    def testSingletonCreation(self):
+        """Create singleton objects."""
+
+        with self.assertRaisesRegex(NotImplementedError, 'SingletonCandidate can not be constructed'):
+
+            sc = SingletonCandidate()
+
+        class NewSingletonImplem(GObject.Object, SingletonCandidate):
+            pass
+
+        nsi = NewSingletonImplem()
+
+        self.assertIsNotNone(nsi)
+
+
+    def testFactoryCreation(self):
+        """Create singleton factories."""
+
+        sf = SingletonFactory()
+
+        self.assertIsNotNone(sf)
+
+        class MyFactory(SingletonFactory):
+            pass
+
+        msf = MyFactory()
+
+        self.assertIsNotNone(msf)
+
+
+    def testSingletonMethods(self):
+        """Test the singleton methods."""
+
+        class IntegerCacheImplem(GObject.Object, SingletonCandidate):
+
+            def __init__(self, val):
+                super().__init__()
+                self._val = val
+
+            def __eq__(self, other):
+                return self._val == other._val
+
+            def __hash__(self):
+                return hash('common-key')
+
+        val_0 = IntegerCacheImplem(0)
+        val_0_bis = IntegerCacheImplem(0)
+        val_1 = IntegerCacheImplem(1)
+
+        self.assertEqual(hash(val_0), hash(val_0_bis))
+        self.assertEqual(hash(val_0), hash(val_1))
+
+        self.assertEqual(val_0.hash(), val_0_bis.hash())
+        self.assertEqual(val_0.hash(), val_1.hash())
+
+        self.assertTrue(val_0 == val_0_bis)
+        self.assertFalse(val_0 == val_1)
+
+
+    def testSingletonFootprint(self):
+        """Check for singleton memory footprint."""
+
+        class IntegerCacheImplem(GObject.Object, SingletonCandidate):
+
+            def __init__(self, val):
+                super().__init__()
+                self._val = val
+
+            def __eq__(self, other):
+                return self._val == other._val
+
+            def __hash__(self):
+                return hash('common-key')
+
+        val_0 = IntegerCacheImplem(0)
+        val_0_bis = IntegerCacheImplem(0)
+        val_1 = IntegerCacheImplem(1)
+
+        sf = SingletonFactory()
+
+        obj = sf.get_instance(val_0)
+
+        self.assertTrue(obj is val_0)
+
+        obj = sf.get_instance(val_0_bis)
+
+        self.assertTrue(obj is val_0)
+
+        obj = sf.get_instance(val_1)
+
+        self.assertTrue(obj is val_1)
-- 
cgit v0.11.2-87-g4458