From 73a09734a145722a3bd6199750fad62b46dd9339 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Tue, 18 May 2021 01:12:59 +0200
Subject: Dead with singleton instances containing singleton instances.

---
 plugins/pychrysalide/glibext/singleton.c | 435 ++++++++++++++++++++++++++++++-
 src/glibext/singleton-int.h              |  21 +-
 src/glibext/singleton.c                  | 352 +++++++++++++++++++++++--
 src/glibext/singleton.h                  |  23 +-
 tests/glibext/singleton.py               |  54 +++-
 5 files changed, 832 insertions(+), 53 deletions(-)

diff --git a/plugins/pychrysalide/glibext/singleton.c b/plugins/pychrysalide/glibext/singleton.c
index 2be1105..c4592ac 100644
--- a/plugins/pychrysalide/glibext/singleton.c
+++ b/plugins/pychrysalide/glibext/singleton.c
@@ -25,6 +25,7 @@
 #include "singleton.h"
 
 
+#include <assert.h>
 #include <pygobject.h>
 
 
@@ -43,15 +44,33 @@
 /* Procède à l'initialisation de l'interface de candidature. */
 static void py_singleton_candidate_interface_init(GSingletonCandidateIface *, gpointer *);
 
+/* Fournit une liste de candidats embarqués par un candidat. */
+static GSingletonCandidate **py_singleton_candidate_list_inner_instances_wrapper(const GSingletonCandidate *, size_t *);
+
+/* Met à jour une liste de candidats embarqués par un candidat. */
+static void py_singleton_candidate_update_inner_instances_wrapper(GSingletonCandidate *, GSingletonCandidate **, size_t);
+
 /* 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 *);
 
+/* Marque un candidat comme figé. */
+static void py_singleton_candidate_set_ro_wrapper(GSingletonCandidate *);
+
+/* Indique si le candidat est figé. */
+static bool py_singleton_candidate_is_ro_wrapper(GSingletonCandidate *);
+
 /* Fournit l'empreinte d'un candidat à une centralisation. */
 static PyObject *py_singleton_candidate_hash(PyObject *, PyObject *);
 
+/* Fournit une liste de candidats embarqués par un candidat. */
+static PyObject *py_singleton_candidate_get_inner_instances(PyObject *, void *);
+
+/* Indique si le candidat est figé. */
+static PyObject *py_singleton_candidate_get_read_only(PyObject *, void *);
+
 /* Effectue une comparaison avec un objet 'SingletonCandidate'. */
 static PyObject *py_singleton_candidate_richcompare(PyObject *, PyObject *, int);
 
@@ -92,8 +111,9 @@ static PyObject *py_singleton_factory_get_instance(PyObject *, PyObject *);
 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"                                   \
+    "The SingletonCandidate class is a required interface for objects"      \
+    " aiming at becoming singleton instances. All shared singletons are"    \
+    " registered within a pychrysalide.glibext.SingletonFactory object.\n"  \
     "\n"                                                                    \
     "The main implemantations come with types derived from"                 \
     " pychrysalide.analysis.DataType.\n"                                    \
@@ -104,12 +124,188 @@ static void py_singleton_candidate_interface_init(GSingletonCandidateIface *ifac
     "        ...\n"                                                         \
     "\n"                                                                    \
     "The following methods have to be defined for new implementations:\n"   \
+    "* pychrysalide.glibext.SingletonCandidate._list_inner_instances();\n"  \
+    "* pychrysalide.glibext.SingletonCandidate._update_inner_instances();\n"\
     "* pychrysalide.glibext.SingletonCandidate.__hash__();\n"               \
-    "* pychrysalide.glibext.SingletonCandidate.__eq__()."
+    "* pychrysalide.glibext.SingletonCandidate.__eq__();\n"                 \
+    "* pychrysalide.glibext.SingletonCandidate._set_read_only();\n"         \
+    "* pychrysalide.glibext.SingletonCandidate._is_read_only().\n"
+
+    iface->update_inner = py_singleton_candidate_update_inner_instances_wrapper;
+    iface->list_inner = py_singleton_candidate_list_inner_instances_wrapper;
 
     iface->hash = py_singleton_candidate___hash__wrapper;
     iface->is_equal = py_singleton_candidate___eq__wrapper;
 
+    iface->set_ro = py_singleton_candidate_set_ro_wrapper;
+    iface->is_ro = py_singleton_candidate_is_ro_wrapper;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                count     = quantité d'instances à l'unicité internes.       *
+*                                                                             *
+*  Description : Fournit une liste de candidats embarqués par un candidat.    *
+*                                                                             *
+*  Retour      : Liste de candidats internes ou NULL si aucun.                *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static GSingletonCandidate **py_singleton_candidate_list_inner_instances_wrapper(const GSingletonCandidate *candidate, size_t *count)
+{
+    GSingletonCandidate **result;           /* Instances à retourner       */
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *pyinstances;                  /* Liste en version Python     */
+    int ret;                                /* Bilan d'un appel            */
+    Py_ssize_t size;                        /* Taille de la liste          */
+    Py_ssize_t i;                           /* Boucle de parcours #1       */
+    PyObject *pyinstance;                   /* Instance interne            */
+    Py_ssize_t k;                           /* Boucle de parcours #2       */
+
+#define SINGLETON_CANDIDATE_LIST_INNER_INSTANCES_WRAPPER PYTHON_WRAPPER_DEF     \
+(                                                                               \
+    _list_inner_instances, "$self, /",                                          \
+    METH_NOARGS,                                                                \
+    "Provide an internal access to the list of optional internal singleton"     \
+    " candidate instances.\n"                                                   \
+    "\n"                                                                        \
+    "The result has to be a tuple containing zero or more"                      \
+    " pychrysalide.glibext.SingletonCandidate instances."                       \
+)
+
+    result = NULL;
+    *count = 0;
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "_list_inner_instances"))
+    {
+        pyinstances = run_python_method(pyobj, "_list_inner_instances", NULL);
+
+        if (pyinstances != NULL)
+        {
+            ret = PyTuple_Check(pyinstances);
+            if (!ret)
+            {
+                PyErr_SetString(PyExc_TypeError, "the _inner_instances attribute must be a tuple");
+                goto done;
+            }
+
+            size = PyTuple_GET_SIZE(pyinstances);
+
+            result = calloc(size, sizeof(GSingletonCandidate *));
+
+            for (i = 0; i < size; i++)
+            {
+                pyinstance = PyTuple_GET_ITEM(pyinstances, i);
+
+                ret = PyObject_IsInstance(pyinstance, (PyObject *)get_python_singleton_candidate_type());
+                if (ret != 1)
+                {
+                    PyErr_SetString(PyExc_TypeError, "the _inner_instances attribute must only contain pychrysalide.glibext.SingletonCandidate instances");
+
+                    for (k = 0; k < i; k++)
+                        g_object_unref(G_OBJECT(result[k]));
+
+                    free(result);
+                    result = NULL;
+
+                    goto done;
+
+                }
+
+                result[i] = G_SINGLETON_CANDIDATE(pygobject_get(pyinstance));
+                assert(result[i] != NULL);
+
+                g_object_ref(G_OBJECT(result[i]));
+
+            }
+
+            *count = size;
+
+ done:
+
+            Py_DECREF(pyinstances);
+
+        }
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                instances = liste de candidats internes devenus singletons.  *
+*                count     = quantité d'instances à l'unicité internes.       *
+*                                                                             *
+*  Description : Met à jour une liste de candidats embarqués par un candidat. *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void py_singleton_candidate_update_inner_instances_wrapper(GSingletonCandidate *candidate, GSingletonCandidate **instances, size_t count)
+{
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *args;                         /* Arguments pour l'appel      */
+    PyObject *pyinstances;                  /* Liste d'instances converties*/
+    size_t i;                               /* Boucle de parcours          */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define SINGLETON_CANDIDATE_UPDATE_INNER_INSTANCES_WRAPPER PYTHON_WRAPPER_DEF           \
+(                                                                                       \
+    _update_inner_instances, "$self, instances, /",                                     \
+    METH_VARARGS,                                                                       \
+    "Update the list of internal singleton candidate instances.\n"                      \
+    "\n"                                                                                \
+    "The provided *instances* are a tuple of pychrysalide.glibext.SingletonCandidate"   \
+    " objets promoted as singletons."                                                   \
+)
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "_update_inner_instances"))
+    {
+        args = PyTuple_New(1);
+
+        pyinstances = PyTuple_New(count);
+        PyTuple_SetItem(args, 0, pyinstances);
+
+        for (i = 0; i < count; i++)
+            PyTuple_SetItem(pyinstances, i, pygobject_new(G_OBJECT(instances[i])));
+
+        pyret = run_python_method(pyobj, "_update_inner_instances", args);
+
+        Py_XDECREF(pyret);
+        Py_DECREF(args);
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
 }
 
 
@@ -132,13 +328,18 @@ static guint py_singleton_candidate___hash__wrapper(const GSingletonCandidate *c
     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."        \
+#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."                \
+    "\n"                                                                \
+    "Inner instances which are listed through the"                      \
+    " pychrysalide.glibext.SingletonCandidate._list_inner_instances()"  \
+    " method do not need to get processed here as they are handled"     \
+    " automatically by the interface core."                             \
 )
 
     result = 0;
@@ -239,6 +440,111 @@ static gboolean py_singleton_candidate___eq__wrapper(const GSingletonCandidate *
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                                                                             *
+*  Description : Marque un candidat comme figé.                               *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void py_singleton_candidate_set_ro_wrapper(GSingletonCandidate *candidate)
+{
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define SINGLETON_CANDIDATE_SET_RO_WRAPPER PYTHON_WRAPPER_DEF   \
+(                                                               \
+    _set_read_only, "$self, /",                        \
+    METH_NOARGS,                                                \
+    "Abstract method used to mark the content of a singleton"   \
+    " candidate as read-only.\n"                                \
+    "\n"                                                        \
+    "The read-only state is mandatory once the candidate is"    \
+    " registered inside a pychrysalide.glibext.SingletonFactory"\
+    " instance as official singleton."                          \
+)
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "_set_read_only"))
+    {
+        pyret = run_python_method(pyobj, "_set_read_only", NULL);
+
+        Py_XDECREF(pyret);
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                                                                             *
+*  Description : Indique si le candidat est figé.                             *
+*                                                                             *
+*  Retour      : true si le contenu du candidat ne peut plus être modifié.    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool py_singleton_candidate_is_ro_wrapper(GSingletonCandidate *candidate)
+{
+    bool result;                            /* Etat à retourner            */
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define SINGLETON_CANDIDATE_IS_RO_WRAPPER PYTHON_WRAPPER_DEF    \
+(                                                               \
+    _is_read_only, "$self, /",                                  \
+    METH_NOARGS,                                                \
+    "Abstract method used to retrieve the status of the data"   \
+    " contained by a singleton candidate.\n"                    \
+    "\n"                                                        \
+    "The retured value is *True* if the candidate is"           \
+    " registered inside a pychrysalide.glibext.SingletonFactory"\
+    " instance as official singleton, *False* otherwise."       \
+)
+
+    result = false;
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(candidate));
+
+    if (has_python_method(pyobj, "_is_read_only"))
+    {
+        pyret = run_python_method(pyobj, "_is_read_only", NULL);
+
+        result = (pyret == Py_True);
+
+        Py_XDECREF(pyret);
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : self = objet dont l'instance se veut unique.                 *
 *                args = adresse non utilisée ici.                             *
 *                                                                             *
@@ -262,6 +568,10 @@ static PyObject *py_singleton_candidate_hash(PyObject *self, PyObject *args)
     METH_NOARGS, py_singleton_candidate,                            \
     "Compute the hash value of the singleton candidate.\n"          \
     "\n"                                                            \
+    "The method relies on the interface core to include in the"     \
+    " process the optional embedded instances which may become"     \
+    " singletons.\n"                                                \
+    "\n"                                                            \
     "The result is an integer value.\n"                             \
     "\n"                                                            \
     "Even if the Python *hash()* method, relying on the"            \
@@ -284,6 +594,99 @@ static PyObject *py_singleton_candidate_hash(PyObject *self, PyObject *args)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : self    = objet Python concerné par l'appel.                 *
+*                closure = non utilisé ici.                                   *
+*                                                                             *
+*  Description : Fournit une liste de candidats embarqués par un candidat.    *
+*                                                                             *
+*  Retour      : Liste de candidats internes, vide si aucun.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_candidate_get_inner_instances(PyObject *self, void *closure)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    GSingletonCandidate *candidate;         /* Mécanismes natifs           */
+    size_t count;                           /* Quantité d'objets internes  */
+    GSingletonCandidate **instances;        /* Liste des embarqués         */
+    size_t i;                               /* Boucle de parcours          */
+
+#define SINGLETON_CANDIDATE_INNER_INSTANCES_ATTRIB PYTHON_GET_DEF_FULL  \
+(                                                                       \
+    inner_instances, py_singleton_candidate,                            \
+    "List of optional internal singleton candidate instances.\n"        \
+    "\n"                                                                \
+    "The result has to be a tuple containing zero or more"              \
+    " pychrysalide.glibext.SingletonCandidate instances."               \
+)
+
+    candidate = G_SINGLETON_CANDIDATE(pygobject_get(self));
+
+    instances = g_singleton_candidate_list_inner_instances(candidate, &count);
+
+    result = PyTuple_New(count);
+
+    for (i = 0; i < count; i++)
+    {
+        PyTuple_SetItem(result, i, pygobject_new(G_OBJECT(instances[i])));
+        g_object_unref(G_OBJECT(instances[i]));
+    }
+
+    if (instances != NULL)
+        free(instances);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self    = objet Python concerné par l'appel.                 *
+*                closure = non utilisé ici.                                   *
+*                                                                             *
+*  Description : Indique si le candidat est figé.                             *
+*                                                                             *
+*  Retour      : true si le contenu du candidat ne peut plus être modifié.    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_singleton_candidate_get_read_only(PyObject *self, void *closure)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    GSingletonCandidate *candidate;         /* Mécanismes natifs           */
+    bool status;                            /* Etat de l'élément consulté  */
+
+#define SINGLETON_CANDIDATE_READ_ONLY_ATTRIB PYTHON_GET_DEF_FULL    \
+(                                                                   \
+    read_only, py_singleton_candidate,                              \
+    "State of the singleton candidate content.\n"                   \
+    "\n"                                                            \
+    "The result is a boolean: *True* if the object is registered"   \
+    " as singleton, *False* otherwise.\n"                           \
+    "\n"                                                            \
+    "Once a singleton, the object must not change its content as"   \
+    " it is a shared instance."                                     \
+)
+
+    candidate = G_SINGLETON_CANDIDATE(pygobject_get(self));
+
+    status = g_singleton_candidate_is_read_only(candidate);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : a  = premier object Python à consulter.                      *
 *                b  = second object Python à consulter.                       *
 *                op = type de comparaison menée.                              *
@@ -300,8 +703,8 @@ static PyObject *py_singleton_candidate_richcompare(PyObject *a, PyObject *b, in
 {
     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    */
+    GSingletonCandidate *cand_a;            /* Premier élément à traiter   */
+    GSingletonCandidate *cand_b;            /* Second élément à traiter    */
     gboolean status;                        /* Résultat d'une comparaison  */
 
     if (op != Py_EQ)
@@ -348,13 +751,19 @@ static PyObject *py_singleton_candidate_richcompare(PyObject *a, PyObject *b, in
 PyTypeObject *get_python_singleton_candidate_type(void)
 {
     static PyMethodDef py_singleton_candidate_methods[] = {
+        SINGLETON_CANDIDATE_LIST_INNER_INSTANCES_WRAPPER,
+        SINGLETON_CANDIDATE_UPDATE_INNER_INSTANCES_WRAPPER,
         SINGLETON_CANDIDATE_HASH_WRAPPER,
         SINGLETON_CANDIDATE_EQ_WRAPPER,
+        SINGLETON_CANDIDATE_SET_RO_WRAPPER,
+        SINGLETON_CANDIDATE_IS_RO_WRAPPER,
         SINGLETON_CANDIDATE_HASH_METHOD,
         { NULL }
     };
 
     static PyGetSetDef py_singleton_candidate_getseters[] = {
+        SINGLETON_CANDIDATE_INNER_INSTANCES_ATTRIB,
+        SINGLETON_CANDIDATE_READ_ONLY_ATTRIB,
         { NULL }
     };
 
@@ -689,7 +1098,7 @@ PyTypeObject *get_python_singleton_factory_type(void)
 
 bool ensure_python_singleton_factory_is_registered(void)
 {
-    PyTypeObject *type;                     /* Type Python 'ObjectSingleton' */
+    PyTypeObject *type;                     /* Type 'SingletonFactory'     */
     PyObject *module;                       /* Module à recompléter        */
     PyObject *dict;                         /* Dictionnaire du module      */
 
diff --git a/src/glibext/singleton-int.h b/src/glibext/singleton-int.h
index ac31a32..49cfecd 100644
--- a/src/glibext/singleton-int.h
+++ b/src/glibext/singleton-int.h
@@ -29,17 +29,23 @@
 
 
 
+/* Fournit une liste de candidats embarqués par un candidat. */
+typedef GSingletonCandidate ** (* list_inner_instances_fc) (const GSingletonCandidate *, size_t *);
+
+/* Met à jour une liste de candidats embarqués par un candidat. */
+typedef void (* update_inner_instances_fc) (GSingletonCandidate *, GSingletonCandidate **, size_t);
+
 /* 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);
+/* Marque un candidat comme figé. */
+typedef void (* set_candidate_ro_fc) (GSingletonCandidate *);
 
-/* Indique si un objet marqué comme unique. */
-typedef bool (* is_candidate_processed_fc) (const GSingletonCandidate *, bool);
+/* Indique si le candidat est figé. */
+typedef bool (* is_candidate_ro_fc) (GSingletonCandidate *);
 
 
 /* Instance d'objet visant à être unique (interface) */
@@ -47,11 +53,14 @@ struct _GSingletonCandidateIface
 {
     GTypeInterface base_iface;              /* A laisser en premier        */
 
+    list_inner_instances_fc list_inner;     /* Récupération d'internes     */
+    update_inner_instances_fc update_inner; /* Mise à jour des éléments    */
+
     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      */
+    set_candidate_ro_fc set_ro;             /* Bascule en mode figé        */
+    is_candidate_ro_fc is_ro;               /* Consultation de l'état      */
 
 };
 
diff --git a/src/glibext/singleton.c b/src/glibext/singleton.c
index f0ce86f..bcd5580 100644
--- a/src/glibext/singleton.c
+++ b/src/glibext/singleton.c
@@ -36,6 +36,18 @@
 /* Procède à l'initialisation de l'interface de rassemblement. */
 static void g_singleton_candidate_default_init(GSingletonCandidateInterface *);
 
+/* Met à jour une liste de candidats embarqués par un candidat. */
+static void g_singleton_candidate_update_inner_instances(GSingletonCandidate *, GSingletonCandidate **, size_t);
+
+/* Fournit l'empreinte d'un candidat à une centralisation. */
+static guint _g_singleton_candidate_hash(GSingletonCandidate *, GList **);
+
+/* Détermine si deux candidats à l'unicité sont identiques. */
+static gboolean _g_singleton_candidate_is_equal(GSingletonCandidate *, GSingletonCandidate *, GList **);
+
+/* Marque un candidat comme figé. */
+static void _g_singleton_candidate_set_read_only(GSingletonCandidate *, GList **);
+
 
 
 /* ------------------------- COLLECTION D'INSTANCES UNIQUES ------------------------- */
@@ -103,6 +115,73 @@ static void g_singleton_candidate_default_init(GSingletonCandidateInterface *ifa
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                count     = quantité d'instances à l'unicité internes.       *
+*                                                                             *
+*  Description : Fournit une liste de candidats embarqués par un candidat.    *
+*                                                                             *
+*  Retour      : Liste de candidats internes ou NULL si aucun.                *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GSingletonCandidate **g_singleton_candidate_list_inner_instances(const GSingletonCandidate *candidate, size_t *count)
+{
+    GSingletonCandidate **result;           /* Instances à retourner       */
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    if (iface->list_inner == NULL)
+    {
+        *count = 0;
+        result = NULL;
+    }
+
+    else
+        result = iface->list_inner(candidate, count);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                instances = liste de candidats internes devenus singletons.  *
+*                count     = quantité d'instances à l'unicité internes.       *
+*                                                                             *
+*  Description : Met à jour une liste de candidats embarqués par un candidat. *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_singleton_candidate_update_inner_instances(GSingletonCandidate *candidate, GSingletonCandidate **instances, size_t count)
+{
+    GSingletonCandidateIface *iface;        /* Interface utilisée          */
+
+    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+    if (iface->update_inner == NULL)
+        assert(iface->list_inner == NULL);
+
+    else
+    {
+        assert(iface->list_inner != NULL);
+        iface->update_inner(candidate, instances, count);
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                processed = liste de candidats déjà traités.                 *
 *                                                                             *
 *  Description : Fournit l'empreinte d'un candidat à une centralisation.      *
 *                                                                             *
@@ -112,14 +191,70 @@ static void g_singleton_candidate_default_init(GSingletonCandidateInterface *ifa
 *                                                                             *
 ******************************************************************************/
 
-guint g_singleton_candidate_hash(const GSingletonCandidate *candidate)
+static guint _g_singleton_candidate_hash(GSingletonCandidate *candidate, GList **processed)
 {
     guint result;                           /* Valeur à retourner          */
+    GList *skip;                            /* Détection de boucle         */
     GSingletonCandidateIface *iface;        /* Interface utilisée          */
+    GSingletonCandidate **children;         /* Instances internes          */
+    size_t count;                           /* Quantité de ces instances   */
+    size_t i;                               /* Boucle de parcours          */
 
-    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+    skip = g_list_find(*processed, candidate);
+
+    if (skip != NULL)
+        result = 0;
+
+    else
+    {
+        iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+        result = iface->hash(candidate);
+
+        *processed = g_list_append(*processed, candidate);
+
+        children = g_singleton_candidate_list_inner_instances(candidate, &count);
+
+        for (i = 0; i < count; i++)
+        {
+            result ^= _g_singleton_candidate_hash(children[i], processed);
+            g_object_unref(G_OBJECT(children[i]));
+        }
+
+        if (children != NULL)
+            free(children);
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  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(GSingletonCandidate *candidate)
+{
+    guint result;                           /* Valeur à retourner          */
+    GList *processed;                       /* Suivi des traitements       */
+
+    processed = NULL;
+
+    result = _g_singleton_candidate_hash(candidate, &processed);
 
-    result = iface->hash(candidate);
+    assert(processed != NULL);
+
+    g_list_free(processed);
 
     return result;
 
@@ -130,6 +265,7 @@ guint g_singleton_candidate_hash(const GSingletonCandidate *candidate)
 *                                                                             *
 *  Paramètres  : candidate = objet dont l'instance se veut unique.            *
 *                other     = second élément à analyser.                       *
+*                processed = liste de candidats déjà traités.                 *
 *                                                                             *
 *  Description : Détermine si deux candidats à l'unicité sont identiques.     *
 *                                                                             *
@@ -139,27 +275,117 @@ guint g_singleton_candidate_hash(const GSingletonCandidate *candidate)
 *                                                                             *
 ******************************************************************************/
 
-gboolean g_singleton_candidate_is_equal(const GSingletonCandidate *candidate, const GSingletonCandidate *other)
+static gboolean _g_singleton_candidate_is_equal(GSingletonCandidate *candidate, GSingletonCandidate *other, GList **processed)
 {
     gboolean result;                        /* Bilan à renvoyer            */
+    GList *skip;                            /* Détection de boucle         */
     GSingletonCandidateIface *iface;        /* Interface utilisée          */
+    GSingletonCandidate **children[2];      /* Instances internes          */
+    size_t count[2];                        /* Quantité de ces instances   */
+    size_t i;                               /* Boucle de parcours          */
 
-    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+    skip = g_list_find(processed[0], candidate);
+
+    if (skip != NULL)
+        result = (g_list_find(processed[1], other) != NULL);
+
+    else
+    {
+        iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+        result = iface->is_equal(candidate, other);
+
+        processed[0] = g_list_append(processed[0], candidate);
+        processed[1] = g_list_append(processed[1], other);
+
+        if (!result)
+            goto done;
+
+        children[0] = g_singleton_candidate_list_inner_instances(candidate, &count[0]);
+        children[1] = g_singleton_candidate_list_inner_instances(other, &count[1]);
+
+        if (count[0] != count[1])
+        {
+            for (i = 0; i < count[0]; i++)
+                g_object_unref(G_OBJECT(children[0][i]));
 
-    result = iface->is_equal(candidate, other);
+            for (i = 0; i < count[1]; i++)
+                g_object_unref(G_OBJECT(children[1][i]));
+
+        }
+
+        else
+        {
+            for (i = 0; i < count[0] && result; i++)
+            {
+                result = _g_singleton_candidate_is_equal(children[0][i], children[1][i], processed);
+                g_object_unref(G_OBJECT(children[0][i]));
+                g_object_unref(G_OBJECT(children[1][i]));
+            }
+
+            for (; i < count[0]; i++)
+            {
+                g_object_unref(G_OBJECT(children[0][i]));
+                g_object_unref(G_OBJECT(children[1][i]));
+            }
+
+            if (children[0] != NULL)
+                free(children[0]);
+
+            if (children[1] != NULL)
+                free(children[1]);
+
+        }
+
+    }
+
+ done:
 
     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(GSingletonCandidate *candidate, GSingletonCandidate *other)
+{
+    gboolean result;                        /* Bilan à renvoyer            */
+    GList *processed[2];                    /* Suivi des traitements       */
+
+    processed[0] = NULL;
+    processed[1] = NULL;
+
+    result = _g_singleton_candidate_is_equal(candidate, other, processed);
+
+    assert(processed[0] != NULL);
+    assert(processed[1] != NULL);
+
+    g_list_free(processed[0]);
+    g_list_free(processed[1]);
+
+    return result;
+
+}
+
 
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : candidate = objet dont l'instance se veut unique.            *
-*                soon      = indique un traitement démarré et en cours.       *
+*                processed = liste de candidats déjà traités.                 *
 *                                                                             *
-*  Description : Marque un candidat comme traité ou en cours de traitement.   *
+*  Description : Marque un candidat comme figé.                               *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -167,13 +393,36 @@ gboolean g_singleton_candidate_is_equal(const GSingletonCandidate *candidate, co
 *                                                                             *
 ******************************************************************************/
 
-void g_singleton_candidate_mark_as_processed(GSingletonCandidate *candidate, bool soon)
+static void _g_singleton_candidate_set_read_only(GSingletonCandidate *candidate, GList **processed)
 {
+    GList *skip;                            /* Détection de boucle         */
     GSingletonCandidateIface *iface;        /* Interface utilisée          */
+    GSingletonCandidate **children;         /* Instances internes          */
+    size_t count;                           /* Quantité de ces instances   */
+    size_t i;                               /* Boucle de parcours          */
 
-    iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+    skip = g_list_find(*processed, candidate);
+
+    if (skip == NULL)
+    {
+        iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
+
+        iface->set_ro(candidate);
+
+        *processed = g_list_append(*processed, candidate);
+
+        children = g_singleton_candidate_list_inner_instances(candidate, &count);
 
-    iface->mark(candidate, soon);
+        for (i = 0; i < count; i++)
+        {
+            _g_singleton_candidate_set_read_only(candidate, processed);
+            g_object_unref(G_OBJECT(children[i]));
+        }
+
+        if (children != NULL)
+            free(children);
+
+    }
 
 }
 
@@ -181,28 +430,53 @@ void g_singleton_candidate_mark_as_processed(GSingletonCandidate *candidate, boo
 /******************************************************************************
 *                                                                             *
 *  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.                     *
+*  Description : Marque un candidat comme figé.                               *
 *                                                                             *
-*  Retour      : true si l'objet est traité ou en phase de l'être, ou false.  *
+*  Retour      : -                                                            *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-bool g_singleton_candidate_is_processed(const GSingletonCandidate *candidate, bool soon)
+void g_singleton_candidate_set_read_only(GSingletonCandidate *candidate)
 {
-    bool result;                            /* Statut à retourner          */
+    GList *processed;                       /* Suivi des traitements       */
+
+    processed = NULL;
+
+    _g_singleton_candidate_set_read_only(candidate, &processed);
+
+    assert(processed != NULL);
+
+    g_list_free(processed);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : candidate = objet dont l'instance se veut unique.            *
+*                                                                             *
+*  Description : Indique si le candidat est figé.                             *
+*                                                                             *
+*  Retour      : true si le contenu du candidat ne peut plus être modifié.    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_singleton_candidate_is_read_only(GSingletonCandidate *candidate)
+{
+    bool result;                            /* Etat à retourner            */
     GSingletonCandidateIface *iface;        /* Interface utilisée          */
 
     iface = G_SINGLETON_CANDIDATE_GET_IFACE(candidate);
 
-    result = iface->is_processed(candidate, soon);
+    result = iface->is_ro(candidate);
 
     return result;
 
-
 }
 
 
@@ -348,10 +622,48 @@ GSingletonFactory *g_singleton_factory_new(void)
 GSingletonCandidate *g_singleton_factory_get_instance(GSingletonFactory *factory, GSingletonCandidate *candidate)
 {
     GSingletonCandidate *result;            /* Instance unique à retourner */
+    size_t count;                           /* Quantité d'objets internes  */
+    GSingletonCandidate **instances;        /* Liste d'instances internes  */
+    GSingletonCandidate **updated;          /* Nouvelle liste d'instances  */
+    bool need_update;                       /* Mise à jour nécessaire      */
+    size_t i;                               /* Boucle de parcours          */
 #ifndef NDEBUG
-    gboolean status;                         /* Validation d'une opération */
+    gboolean status;                        /* Validation d'une opération  */
 #endif
 
+    /* Validation des objets internes éventuels */
+
+    instances = g_singleton_candidate_list_inner_instances(candidate, &count);
+
+    if (count > 0)
+    {
+        updated = malloc(count * sizeof(GSingletonCandidate *));
+        need_update = false;
+
+        for (i = 0; i < count; i++)
+        {
+            updated[i] = g_singleton_factory_get_instance(factory, instances[i]);
+            need_update |= (instances[i] != updated[i]);
+        }
+
+        if (need_update)
+            g_singleton_candidate_update_inner_instances(candidate, updated, count);
+
+        for (i = 0; i < count; i++)
+        {
+            g_object_unref(G_OBJECT(updated[i]));
+            g_object_unref(G_OBJECT(instances[i]));
+        }
+
+        free(updated);
+
+    }
+
+    if (instances != NULL)
+        free(instances);
+
+    /* Récupération de l'instance principale */
+
     g_mutex_lock(&factory->access);
 
     if (g_hash_table_contains(factory->table, candidate))
@@ -376,6 +688,8 @@ GSingletonCandidate *g_singleton_factory_get_instance(GSingletonFactory *factory
         g_hash_table_add(factory->table, candidate);
 #endif
 
+        g_singleton_candidate_set_read_only(candidate);
+
         result = candidate;
 
     }
diff --git a/src/glibext/singleton.h b/src/glibext/singleton.h
index 6de9f41..0561f80 100644
--- a/src/glibext/singleton.h
+++ b/src/glibext/singleton.h
@@ -30,6 +30,11 @@
 
 
 
+/* Définition d'un compacteur d'instances de types (instance) */
+typedef struct _GSingletonFactory GSingletonFactory;
+
+
+
 /* ------------------ INTERFACE POUR CANDIDAT A UNE CENTRALISATION ------------------ */
 
 
@@ -51,17 +56,20 @@ 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 une liste de candidats embarqués par un candidat. */
+GSingletonCandidate **g_singleton_candidate_list_inner_instances(const GSingletonCandidate *, size_t *);
+
 /* Fournit l'empreinte d'un candidat à une centralisation. */
-guint g_singleton_candidate_hash(const GSingletonCandidate *);
+guint g_singleton_candidate_hash(GSingletonCandidate *);
 
 /* Détermine si deux candidats à l'unicité sont identiques. */
-gboolean g_singleton_candidate_is_equal(const GSingletonCandidate *, const GSingletonCandidate *);
+gboolean g_singleton_candidate_is_equal(GSingletonCandidate *, GSingletonCandidate *);
 
-/* Marque un candidat comme traité ou en cours de traitement. */
-void g_singleton_candidate_mark_as_processed(GSingletonCandidate *, bool);
+/* Marque un candidat comme figé. */
+void g_singleton_candidate_set_read_only(GSingletonCandidate *);
 
-/* Indique si un objet marqué comme unique. */
-bool g_singleton_candidate_is_processed(const GSingletonCandidate *, bool);
+/* Indique si le candidat est figé. */
+bool g_singleton_candidate_is_read_only(GSingletonCandidate *);
 
 
 
@@ -76,9 +84,6 @@ bool g_singleton_candidate_is_processed(const GSingletonCandidate *, bool);
 #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;
 
diff --git a/tests/glibext/singleton.py b/tests/glibext/singleton.py
index b0608f0..4588ae5 100644
--- a/tests/glibext/singleton.py
+++ b/tests/glibext/singleton.py
@@ -47,12 +47,15 @@ class TestSingleton(ChrysalideTestCase):
                 super().__init__()
                 self._val = val
 
-            def __eq__(self, other):
-                return self._val == other._val
+            def _list_inner_instances(self):
+                return ()
 
             def __hash__(self):
                 return hash('common-key')
 
+            def __eq__(self, other):
+                return self._val == other._val
+
         val_0 = IntegerCacheImplem(0)
         val_0_bis = IntegerCacheImplem(0)
         val_1 = IntegerCacheImplem(1)
@@ -70,24 +73,31 @@ class TestSingleton(ChrysalideTestCase):
     def testSingletonFootprint(self):
         """Check for singleton memory footprint."""
 
+        sf = SingletonFactory()
+
+
         class IntegerCacheImplem(GObject.Object, SingletonCandidate):
 
             def __init__(self, val):
                 super().__init__()
                 self._val = val
 
-            def __eq__(self, other):
-                return self._val == other._val
+            def _list_inner_instances(self):
+                return ()
 
             def __hash__(self):
                 return hash('common-key')
 
+            def __eq__(self, other):
+                return self._val == other._val
+
+            def _set_read_only(self):
+                pass
+
         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)
@@ -99,3 +109,35 @@ class TestSingleton(ChrysalideTestCase):
         obj = sf.get_instance(val_1)
 
         self.assertTrue(obj is val_1)
+
+        self.assertEqual(len(obj.inner_instances), 0)
+
+
+        class MasterCacheImplem(GObject.Object, SingletonCandidate):
+
+            def __init__(self, children):
+                super().__init__()
+                self._children = children
+
+            def _list_inner_instances(self):
+                return self._children
+
+            def _update_inner_instances(self, instances):
+                self._children = instances
+
+            def __hash__(self):
+                return hash('master-key')
+
+            def __eq__(self, other):
+                return False
+
+            def _set_read_only(self):
+                pass
+
+        master = MasterCacheImplem(( val_0_bis, val_1 ))
+
+        obj = sf.get_instance(master)
+
+        self.assertTrue(obj.inner_instances[0] is val_0)
+
+        self.assertTrue(obj.inner_instances[1] is val_1)
-- 
cgit v0.11.2-87-g4458