Create a second GPtrArray for the keys. We don't want to use
[platform/upstream/evolution-data-server.git] / camel / camel-object.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- *
2  *
3  * Author:
4  *  Michael Zucchi <notzed@ximian.com>
5  *
6  * Copyright 2000-2002 Ximian, Inc. (www.ximian.com)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20  * USA
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <string.h>
29 #include "camel-object.h"
30
31 #include <e-util/e-memory.h>
32
33 #ifdef ENABLE_THREADS
34 #include <pthread.h>
35 #include <semaphore.h>
36 #include <e-util/e-msgport.h>
37 #endif
38
39 #define d(x)
40
41 /* I just mashed the keyboard for these... */
42 #define CAMEL_OBJECT_MAGIC               0x77A344ED
43 #define CAMEL_OBJECT_CLASS_MAGIC         0xEE26A997
44 #define CAMEL_OBJECT_FINALISED_MAGIC       0x84AC365F
45 #define CAMEL_OBJECT_CLASS_FINALISED_MAGIC 0x7621ABCD
46
47 /* ** Quickie type system ************************************************* */
48
49 /* A 'locked' hooklist, that is only allocated on demand */
50 typedef struct _CamelHookList {
51         EMutex *lock;
52
53         unsigned int depth:30;  /* recursive event depth */
54         unsigned int flags:2;   /* flags, see below */
55
56         unsigned int list_length;
57         struct _CamelHookPair *list;
58 } CamelHookList;
59
60 #define CAMEL_HOOK_PAIR_REMOVED (1<<0)
61
62 /* a 'hook pair', actually a hook tuple, we just store all hooked events in the same list,
63    and just comapre as we go, rather than storing separate lists for each hook type
64
65    the name field just points directly to the key field in the class's preplist hashtable.
66    This way we can just use a direct pointer compare when scanning it, and also saves
67    copying the string */
68 typedef struct _CamelHookPair
69 {
70         struct _CamelHookPair *next; /* next MUST be the first member */
71
72         unsigned int id:30;
73         unsigned int flags:2;   /* removed, etc */
74
75         const char *name;       /* points to the key field in the classes preplist, static memory */
76         union {
77                 CamelObjectEventHookFunc event;
78                 CamelObjectEventPrepFunc prep;
79         } func;
80         void *data;
81 } CamelHookPair;
82
83 struct _CamelObjectBag {
84         GHashTable *object_table; /* object by key */
85         GHashTable *key_table;  /* key by object */
86         CamelCopyFunc copy_key;
87         GFreeFunc free_key;
88 #ifdef ENABLE_THREADS
89         pthread_t owner;        /* the thread that has reserved the bag for a new entry */
90         sem_t reserve_sem;      /* used to track ownership */
91 #endif
92 };
93
94 /* used to tag a bag hookpair */
95 static const char *bag_name = "object:bag";
96
97 /* ********************************************************************** */
98
99 static CamelHookList *camel_object_get_hooks(CamelObject *o);
100 static void camel_object_free_hooks(CamelObject *o);
101 static void camel_object_bag_remove_unlocked(CamelObjectBag *inbag, CamelObject *o, CamelHookList *hooks);
102
103 #ifdef ENABLE_THREADS
104 #define camel_object_unget_hooks(o) (e_mutex_unlock((CAMEL_OBJECT(o)->hooks->lock)))
105 #else
106 #define camel_object_unget_hooks(o)
107 #endif
108
109 /* ********************************************************************** */
110
111 static pthread_mutex_t chunks_lock = PTHREAD_MUTEX_INITIALIZER;
112
113 static EMemChunk *pair_chunks;
114 static EMemChunk *hook_chunks;
115 static unsigned int pair_id = 1;
116
117 static EMutex *type_lock;
118
119 static GHashTable *type_table;
120 static EMemChunk *type_chunks;
121
122 CamelType camel_object_type;
123
124 #ifdef ENABLE_THREADS
125 #define P_LOCK(l) (pthread_mutex_lock(&l))
126 #define P_UNLOCK(l) (pthread_mutex_unlock(&l))
127 #define E_LOCK(l) (e_mutex_lock(l))
128 #define E_UNLOCK(l) (e_mutex_unlock(l))
129 #define CLASS_LOCK(k) (g_mutex_lock((((CamelObjectClass *)k)->lock)))
130 #define CLASS_UNLOCK(k) (g_mutex_unlock((((CamelObjectClass *)k)->lock)))
131 #else
132 #define P_LOCK(l)
133 #define P_UNLOCK(l)
134 #define E_LOCK(l)
135 #define E_UNLOCK(l)
136 #define CLASS_LOCK(k)
137 #define CLASS_UNLOCK(k)
138 #endif
139
140 static struct _CamelHookPair *
141 pair_alloc(void)
142 {
143         CamelHookPair *pair;
144
145         P_LOCK(chunks_lock);
146         pair = e_memchunk_alloc(pair_chunks);
147         pair->id = pair_id++;
148         if (pair_id == 0)
149                 pair_id = 1;
150         P_UNLOCK(chunks_lock);
151
152         return pair;
153 }
154
155 static void
156 pair_free(CamelHookPair *pair)
157 {
158         g_assert(pair_chunks != NULL);
159
160         P_LOCK(chunks_lock);
161         e_memchunk_free(pair_chunks, pair);
162         P_UNLOCK(chunks_lock);
163 }
164
165 static struct _CamelHookList *
166 hooks_alloc(void)
167 {
168         CamelHookList *hooks;
169
170         P_LOCK(chunks_lock);
171         hooks = e_memchunk_alloc(hook_chunks);
172         P_UNLOCK(chunks_lock);
173
174         return hooks;
175 }
176
177 static void
178 hooks_free(CamelHookList *hooks)
179 {
180         g_assert(hook_chunks != NULL);
181
182         P_LOCK(chunks_lock);
183         e_memchunk_free(hook_chunks, hooks);
184         P_UNLOCK(chunks_lock);
185 }
186
187 /* not checked locked, who cares, only required for people that want to redefine root objects */
188 void
189 camel_type_init(void)
190 {
191         static int init = FALSE;
192
193         if (init)
194                 return;
195
196         init = TRUE;
197         pair_chunks = e_memchunk_new(16, sizeof(CamelHookPair));
198         hook_chunks = e_memchunk_new(16, sizeof(CamelHookList));
199         type_lock = e_mutex_new(E_MUTEX_REC);
200         type_chunks = e_memchunk_new(32, sizeof(CamelType));
201         type_table = g_hash_table_new(NULL, NULL);
202 }
203
204 /* ************************************************************************ */
205
206 /* Should this return the object to the caller? */
207 static void
208 cobject_init (CamelObject *o, CamelObjectClass *klass)
209 {
210         o->klass = klass;
211         o->magic = CAMEL_OBJECT_MAGIC;
212         o->ref_count = 1;
213         o->flags = 0;
214 }
215
216 static void
217 cobject_finalise(CamelObject *o)
218 {
219         /*printf("%p: finalise %s\n", o, o->klass->name);*/
220
221         g_assert(o->ref_count == 0);
222
223         camel_object_free_hooks(o);
224
225         o->magic = CAMEL_OBJECT_FINALISED_MAGIC;
226         o->klass = NULL;
227 }
228
229 static int
230 cobject_getv(CamelObject *o, CamelException *ex, CamelArgGetV *args)
231 {
232         int i;
233         guint32 tag;
234
235         for (i=0;i<args->argc;i++) {
236                 CamelArgGet *arg = &args->argv[i];
237
238                 tag = arg->tag;
239
240                 switch (tag & CAMEL_ARG_TAG) {
241                 case CAMEL_OBJECT_ARG_DESCRIPTION:
242                         *arg->ca_str = (char *)o->klass->name;
243                         break;
244                 }
245         }
246
247         /* could have flags or stuff here? */
248         return 0;
249 }
250
251 static int
252 cobject_setv(CamelObject *o, CamelException *ex, CamelArgV *args)
253 {
254         /* could have flags or stuff here? */
255         return 0;
256 }
257
258 static void
259 cobject_free(CamelObject *o, guint32 tag, void *value)
260 {
261         /* do nothing */
262 }
263
264 static void
265 cobject_class_init(CamelObjectClass *klass)
266 {
267         klass->magic = CAMEL_OBJECT_CLASS_MAGIC;
268
269         klass->getv = cobject_getv;
270         klass->setv = cobject_setv;
271         klass->free = cobject_free;
272
273         camel_object_class_add_event(klass, "finalize", NULL);
274 }
275
276 static void
277 cobject_class_finalise(CamelObjectClass * klass)
278 {
279         klass->magic = CAMEL_OBJECT_CLASS_FINALISED_MAGIC;
280
281         g_free(klass);
282 }
283
284 CamelType
285 camel_object_get_type (void)
286 {
287         if (camel_object_type == CAMEL_INVALID_TYPE) {
288                 camel_type_init();
289
290                 camel_object_type = camel_type_register(NULL, "CamelObject", /*, 0, 0*/
291                                                         sizeof(CamelObject), sizeof(CamelObjectClass),
292                                                         cobject_class_init, cobject_class_finalise,
293                                                         cobject_init, cobject_finalise);
294         }
295
296         return camel_object_type;
297 }
298
299 static void
300 camel_type_class_init(CamelObjectClass *klass, CamelObjectClass *type)
301 {
302         if (type->parent)
303                 camel_type_class_init(klass, type->parent);
304
305         if (type->klass_init)
306                 type->klass_init(klass);
307 }
308
309 CamelType
310 camel_type_register (CamelType parent, const char * name,
311                      /*unsigned int ver, unsigned int rev,*/
312                      size_t object_size, size_t klass_size,
313                      CamelObjectClassInitFunc class_init,
314                      CamelObjectClassFinalizeFunc class_finalise,
315                      CamelObjectInitFunc object_init,
316                      CamelObjectFinalizeFunc object_finalise)
317 {
318         CamelObjectClass *klass;
319         /*int offset;
320           size_t size;*/
321
322         if (parent != NULL && parent->magic != CAMEL_OBJECT_CLASS_MAGIC) {
323                 g_warning("camel_type_register: invalid junk parent class for '%s'", name);
324                 return NULL;
325         }
326
327         E_LOCK(type_lock);
328
329         /* Have to check creation, it might've happened in another thread before we got here */
330         klass = g_hash_table_lookup(type_table, name);
331         if (klass != NULL) {
332                 if (klass->klass_size != klass_size || klass->object_size != object_size
333                     || klass->klass_init != class_init || klass->klass_finalise != class_finalise
334                     || klass->init != object_init || klass->finalise != object_finalise) {
335                         g_warning("camel_type_register: Trying to re-register class '%s'", name);
336                         klass = NULL;
337                 }
338                 E_UNLOCK(type_lock);
339                 return klass;
340         }
341
342         /* this is for objects with no parent as part of their struct ('interfaces'?) */
343         /*offset = parent?parent->klass_size:0;
344         offset = (offset + 3) & (~3);
345
346         size = offset + klass_size;
347
348         klass = g_malloc0(size);
349
350         klass->klass_size = size;
351         klass->klass_data = offset;
352
353         offset = parent?parent->object_size:0;
354         offset = (offset + 3) & (~3);
355
356         klass->object_size = offset + object_size;
357         klass->object_data = offset;*/
358
359         if (parent
360             && klass_size < parent->klass_size) {
361                 g_warning("camel_type_register: '%s' has smaller class size than parent '%s'", name, parent->name);
362                 E_UNLOCK(type_lock);
363                 return NULL;
364         }
365
366         klass = g_malloc0(klass_size);
367         klass->klass_size = klass_size;
368         klass->object_size = object_size;       
369 #ifdef ENABLE_THREADS
370         klass->lock = g_mutex_new();
371 #endif
372         klass->instance_chunks = e_memchunk_new(8, object_size);
373
374         klass->parent = parent;
375         if (parent) {
376                 klass->next = parent->child;
377                 parent->child = klass;
378         }
379         klass->name = name;
380
381         /*klass->version = ver;
382           klass->revision = rev;*/
383
384         klass->klass_init = class_init;
385         klass->klass_finalise = class_finalise;
386
387         klass->init = object_init;
388         klass->finalise = object_finalise;
389
390         /* setup before class init, incase class init func uses the type or looks it up ? */
391         g_hash_table_insert(type_table, (void *)name, klass);
392
393         camel_type_class_init(klass, klass);
394
395         E_UNLOCK(type_lock);
396
397         return klass;
398 }
399
400 static void
401 camel_object_init(CamelObject *o, CamelObjectClass *klass, CamelType type)
402 {
403         if (type->parent)
404                 camel_object_init(o, klass, type->parent);
405
406         if (type->init)
407                 type->init(o, klass);
408 }
409
410 CamelObject *
411 camel_object_new(CamelType type)
412 {
413         CamelObject *o;
414
415         if (type == NULL)
416                 return NULL;
417
418         if (type->magic != CAMEL_OBJECT_CLASS_MAGIC)
419                 return NULL;
420
421         CLASS_LOCK(type);
422
423         o = e_memchunk_alloc0(type->instance_chunks);
424
425 #ifdef CAMEL_OBJECT_TRACK_INSTANCES
426         if (type->instances)
427                 type->instances->prev = o;
428         o->next = type->instances;
429         o->prev = NULL;
430         type->instances = o;
431 #endif
432
433         CLASS_UNLOCK(type);
434
435         camel_object_init(o, type, type);
436
437         d(printf("%p: new %s()\n", o, o->klass->name));
438
439         return o;
440 }
441
442 void
443 camel_object_ref(void *vo)
444 {
445         register CamelObject *o = vo;
446
447         g_return_if_fail(CAMEL_IS_OBJECT(o));
448
449         E_LOCK(type_lock);
450
451         o->ref_count++;
452         d(printf("%p: ref %s(%d)\n", o, o->klass->name, o->ref_count));
453
454         E_UNLOCK(type_lock);
455 }
456
457 void
458 camel_object_unref(void *vo)
459 {
460         register CamelObject *o = vo;
461         register CamelObjectClass *klass, *k;
462         CamelHookList *hooks = NULL;
463
464         g_return_if_fail(CAMEL_IS_OBJECT(o));
465         
466         klass = o->klass;
467
468         if (o->hooks)
469                 hooks = camel_object_get_hooks(o);
470
471         E_LOCK(type_lock);
472
473         o->ref_count--;
474
475         d(printf("%p: unref %s(%d)\n", o, o->klass->name, o->ref_count));
476
477         if (o->ref_count > 0
478             || (o->flags & CAMEL_OBJECT_DESTROY)) {
479                 E_UNLOCK(type_lock);
480                 if (hooks)
481                         camel_object_unget_hooks(o);
482                 return;
483         }
484
485         o->flags |= CAMEL_OBJECT_DESTROY;
486
487         if (hooks)
488                 camel_object_bag_remove_unlocked(NULL, o, hooks);
489
490         E_UNLOCK(type_lock);
491
492         if (hooks)
493                 camel_object_unget_hooks(o);
494
495         camel_object_trigger_event(o, "finalize", NULL);
496
497         k = klass;
498         while (k) {
499                 if (k->finalise)
500                         k->finalise(o);
501                 k = k->parent;
502         }
503
504         o->magic = CAMEL_OBJECT_FINALISED_MAGIC;
505
506         CLASS_LOCK(klass);
507 #ifdef CAMEL_OBJECT_TRACK_INSTANCES
508         if (o->prev)
509                 o->prev->next = o->next;
510         else
511                 klass->instances = o->next;
512         if (o->next)
513                 o->next->prev = o->prev;
514 #endif
515         e_memchunk_free(klass->instance_chunks, o);
516         CLASS_UNLOCK(klass);
517 }
518
519 const char *
520 camel_type_to_name (CamelType type)
521 {
522         if (type == NULL)
523                 return "(NULL class)";
524
525         if (type->magic == CAMEL_OBJECT_CLASS_MAGIC)
526                 return type->name;
527
528         return "(Junk class)";
529 }
530
531 CamelType camel_name_to_type(const char *name)
532 {
533         /* TODO: Load a class off disk (!) */
534
535         return g_hash_table_lookup(type_table, name);
536 }
537
538 static char *
539 desc_data(CamelObject *o, int ok)
540 {
541         char *what;
542
543         if (o == NULL)
544                 what = g_strdup("NULL OBJECT");
545         else if (o->magic == ok)
546                 what = NULL;
547         else if (o->magic == CAMEL_OBJECT_MAGIC)
548                 what = g_strdup_printf("CLASS '%s'", ((CamelObjectClass *)o)->name);
549         else if (o->magic == CAMEL_OBJECT_CLASS_MAGIC)
550                 what = g_strdup_printf("CLASS '%s'", ((CamelObjectClass *)o)->name);
551         else if (o->magic == CAMEL_OBJECT_FINALISED_MAGIC)
552                 what = g_strdup_printf("finalised OBJECT");
553         else if (o->magic == CAMEL_OBJECT_CLASS_FINALISED_MAGIC)
554                 what = g_strdup_printf("finalised CLASS");
555         else 
556                 what = g_strdup_printf("junk data");
557
558         return what;
559 }
560
561 static gboolean
562 check_magic(void *o, CamelType ctype, int isob)
563 {
564         char *what, *to;
565
566         what = desc_data(o, isob?CAMEL_OBJECT_MAGIC:CAMEL_OBJECT_CLASS_MAGIC);
567         to = desc_data((CamelObject *)ctype, CAMEL_OBJECT_CLASS_MAGIC);
568
569         if (what || to) {
570                 if (what == NULL) {
571                         if (isob)
572                                 what = g_strdup_printf("OBJECT '%s'", ((CamelObject *)o)->klass->name);
573                         else
574                                 what = g_strdup_printf("OBJECT '%s'", ((CamelObjectClass *)o)->name);
575                 }               
576                 if (to == NULL)
577                         to = g_strdup_printf("OBJECT '%s'", ctype->name);
578                 g_warning("Trying to check %s is %s", what, to);
579                 g_free(what);
580                 g_free(to);
581
582                 return FALSE;
583         }
584
585         return TRUE;
586 }
587
588 gboolean
589 camel_object_is (CamelObject *o, CamelType ctype)
590 {
591         CamelObjectClass *k;
592
593         g_return_val_if_fail(check_magic(o, ctype, TRUE), FALSE);
594
595         k = o->klass;
596         while (k) {
597                 if (k == ctype)
598                         return TRUE;
599                 k = k->parent;
600         }
601
602         return FALSE;
603 }
604
605 gboolean
606 camel_object_class_is (CamelObjectClass *k, CamelType ctype)
607 {
608         g_return_val_if_fail(check_magic(k, ctype, FALSE), FALSE);
609
610         while (k) {
611                 if (k == ctype)
612                         return TRUE;
613                 k = k->parent;
614         }
615
616         return FALSE;
617 }
618
619 CamelObject *
620 camel_object_cast(CamelObject *o, CamelType ctype)
621 {
622         CamelObjectClass *k;
623
624         g_return_val_if_fail(check_magic(o, ctype, TRUE), NULL);
625
626         k = o->klass;
627         while (k) {
628                 if (k == ctype)
629                         return o;
630                 k = k->parent;
631         }
632
633         g_warning("Object %p (class '%s') doesn't have '%s' in its hierarchy", o, o->klass->name, ctype->name);
634
635         return NULL;
636 }
637
638 CamelObjectClass *
639 camel_object_class_cast(CamelObjectClass *k, CamelType ctype)
640 {
641         CamelObjectClass *r = k;
642
643         g_return_val_if_fail(check_magic(k, ctype, FALSE), NULL);
644
645         while (k) {
646                 if (k == ctype)
647                         return r;
648                 k = k->parent;
649         }
650
651         g_warning("Class '%s' doesn't have '%s' in its hierarchy", r->name, ctype->name);
652
653         return NULL;
654 }
655
656 void
657 camel_object_class_add_event(CamelObjectClass *klass, const char *name, CamelObjectEventPrepFunc prep)
658 {
659         CamelHookPair *pair;
660
661         g_return_if_fail (name);
662
663         pair = klass->hooks;
664         while (pair) {
665                 if (strcmp(pair->name, name) == 0) {
666                         g_warning("camel_object_class_add_event: `%s' is already declared for '%s'\n",
667                                   name, klass->name);
668                         return;
669                 }
670                 pair = pair->next;
671         }
672
673         pair = pair_alloc();
674         pair->name = name;
675         pair->func.prep = prep;
676         pair->flags = 0;
677
678         pair->next = klass->hooks;
679         klass->hooks = pair;
680 }
681
682 /* free hook data */
683 static void camel_object_free_hooks(CamelObject *o)
684 {
685         CamelHookPair *pair, *next;
686
687         if (o->hooks) {
688                 g_assert(o->hooks->depth == 0);
689                 g_assert((o->hooks->flags & CAMEL_HOOK_PAIR_REMOVED) == 0);
690
691                 pair = o->hooks->list;
692                 while (pair) {
693                         next = pair->next;
694                         pair_free(pair);
695                         pair = next;
696                 }
697                 e_mutex_destroy(o->hooks->lock);
698                 hooks_free(o->hooks);
699                 o->hooks = NULL;
700         }
701 }
702
703 /* return (allocate if required) the object's hook list, locking at the same time */
704 static CamelHookList *camel_object_get_hooks(CamelObject *o)
705 {
706 #ifdef ENABLE_THREADS
707         static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
708 #endif
709         CamelHookList *hooks;
710
711         /* if we have it, we dont have to do any other locking,
712            otherwise use a global lock to setup the object's hook data */
713 #ifdef ENABLE_THREADS
714         if (o->hooks == NULL) {
715                 pthread_mutex_lock(&lock);
716 #endif
717                 if (o->hooks == NULL) {
718                         hooks = hooks_alloc();
719 #ifdef ENABLE_THREADS
720                         hooks->lock = e_mutex_new(E_MUTEX_REC);
721 #endif
722                         hooks->flags = 0;
723                         hooks->depth = 0;
724                         hooks->list_length = 0;
725                         hooks->list = NULL;
726                         o->hooks = hooks;
727                 }
728 #ifdef ENABLE_THREADS
729                 pthread_mutex_unlock(&lock);
730         }
731 #endif
732
733 #ifdef ENABLE_THREADS
734         e_mutex_lock(o->hooks->lock);
735 #endif
736         return o->hooks;        
737 }
738
739 unsigned int
740 camel_object_hook_event(void *vo, const char * name, CamelObjectEventHookFunc func, void *data)
741 {
742         CamelObject *obj = vo;
743         CamelHookPair *pair, *hook;
744         CamelHookList *hooks;
745         int id;
746
747         g_return_val_if_fail(CAMEL_IS_OBJECT (obj), 0);
748         g_return_val_if_fail(name != NULL, 0);
749         g_return_val_if_fail(func != NULL, 0);
750
751         hook = obj->klass->hooks;
752         while (hook) {
753                 if (strcmp(hook->name, name) == 0)
754                         goto setup;
755                 hook = hook->next;
756         }
757
758         g_warning("camel_object_hook_event: trying to hook event `%s' in class `%s' with no defined events.",
759                   name, obj->klass->name);
760
761         return 0;
762
763 setup:
764         /* setup hook pair */
765         pair = pair_alloc();
766         pair->name = hook->name;        /* effectively static! */
767         pair->func.event = func;
768         pair->data = data;
769         pair->flags = 0;
770         id = pair->id;
771
772         /* get the hook list object, locked, link in new event hook, unlock */
773         hooks = camel_object_get_hooks(obj);
774         pair->next = hooks->list;
775         hooks->list = pair;
776         hooks->list_length++;
777         camel_object_unget_hooks(obj);
778
779         return id;
780 }
781
782 void
783 camel_object_remove_event(void *vo, unsigned int id)
784 {
785         CamelObject *obj = vo;
786         CamelHookList *hooks;
787         CamelHookPair *pair, *parent;
788
789         g_return_if_fail (CAMEL_IS_OBJECT (obj));
790         g_return_if_fail (id != 0);
791
792         if (obj->hooks == NULL) {
793                 g_warning("camel_object_unhook_event: trying to unhook `%d` from an instance of `%s' with no hooks",
794                           id, obj->klass->name);
795                 return;
796         }
797
798         /* scan hooks for this event, remove it, or flag it if we're busy */
799         hooks = camel_object_get_hooks(obj);
800         parent = (CamelHookPair *)&hooks->list;
801         pair = parent->next;
802         while (pair) {
803                 if (pair->id == id
804                     && (pair->flags & CAMEL_HOOK_PAIR_REMOVED) == 0) {
805                         if (hooks->depth > 0) {
806                                 pair->flags |= CAMEL_HOOK_PAIR_REMOVED;
807                                 hooks->flags |= CAMEL_HOOK_PAIR_REMOVED;
808                         } else {
809                                 parent->next = pair->next;
810                                 pair_free(pair);
811                                 hooks->list_length--;
812                         }
813                         camel_object_unget_hooks(obj);
814                         return;
815                 }
816                 parent = pair;
817                 pair = pair->next;
818         }
819         camel_object_unget_hooks(obj);
820
821         g_warning("camel_object_unhook_event: cannot find hook id %d in instance of `%s'",
822                   id, obj->klass->name);
823 }
824
825 void
826 camel_object_unhook_event(void *vo, const char * name, CamelObjectEventHookFunc func, void *data)
827 {
828         CamelObject *obj = vo;
829         CamelHookList *hooks;
830         CamelHookPair *pair, *parent;
831
832         g_return_if_fail (CAMEL_IS_OBJECT (obj));
833         g_return_if_fail (name != NULL);
834         g_return_if_fail (func != NULL);
835
836         if (obj->hooks == NULL) {
837                 g_warning("camel_object_unhook_event: trying to unhook `%s` from an instance of `%s' with no hooks",
838                           name, obj->klass->name);
839                 return;
840         }
841
842         /* scan hooks for this event, remove it, or flag it if we're busy */
843         hooks = camel_object_get_hooks(obj);
844         parent = (CamelHookPair *)&hooks->list;
845         pair = parent->next;
846         while (pair) {
847                 if (pair->func.event == func
848                     && pair->data == data
849                     && strcmp(pair->name, name) == 0
850                     && (pair->flags & CAMEL_HOOK_PAIR_REMOVED) == 0) {
851                         if (hooks->depth > 0) {
852                                 pair->flags |= CAMEL_HOOK_PAIR_REMOVED;
853                                 hooks->flags |= CAMEL_HOOK_PAIR_REMOVED;
854                         } else {
855                                 parent->next = pair->next;
856                                 pair_free(pair);
857                                 hooks->list_length--;
858                         }
859                         camel_object_unget_hooks(obj);
860                         return;
861                 }
862                 parent = pair;
863                 pair = pair->next;
864         }
865         camel_object_unget_hooks(obj);
866
867         g_warning("camel_object_unhook_event: cannot find hook/data pair %p/%p in an instance of `%s' attached to `%s'",
868                   func, data, obj->klass->name, name);
869 }
870
871 void
872 camel_object_trigger_event(void *vo, const char * name, void *event_data)
873 {
874         CamelObject *obj = vo;
875         CamelHookList *hooks;
876         CamelHookPair *pair, **pairs, *parent, *hook;
877         int i, size;
878         const char *prepname;
879
880         g_return_if_fail (CAMEL_IS_OBJECT (obj));
881         g_return_if_fail (name);
882
883         hook = obj->klass->hooks;
884         while (hook) {
885                 if (strcmp(hook->name, name) == 0)
886                         goto trigger;
887                 hook = hook->next;
888         }
889
890         g_warning("camel_object_trigger_event: trying to trigger unknown event `%s' in class `%s'",
891                   name, obj->klass->name);
892
893         return;
894
895 trigger:
896         /* try prep function, if false, then quit */
897         if (hook->func.prep != NULL && !hook->func.prep(obj, event_data))
898                 return;
899
900         /* also, no hooks, dont bother going further */
901         if (obj->hooks == NULL)
902                 return;
903
904         /* lock the object for hook emission */
905         camel_object_ref(obj);
906         hooks = camel_object_get_hooks(obj);
907         
908         if (hooks->list) {
909                 /* first, copy the items in the list, and say we're in an event */
910                 hooks->depth++;
911                 pair = hooks->list;
912                 size = 0;
913                 pairs = alloca(sizeof(pairs[0]) * hooks->list_length);
914                 prepname = hook->name;
915                 while (pair) {
916                         if (pair->name == prepname)
917                                 pairs[size++] = pair;
918                         pair = pair->next;
919                 }
920
921                 /* now execute the events we have, if they haven't been removed during our calls */
922                 for (i=size-1;i>=0;i--) {
923                         pair = pairs[i];
924                         if ((pair->flags & CAMEL_HOOK_PAIR_REMOVED) == 0)
925                                 (pair->func.event) (obj, event_data, pair->data);
926                 }
927                 hooks->depth--;
928
929                 /* and if we're out of any events, then clean up any pending removes */
930                 if (hooks->depth == 0 && (hooks->flags & CAMEL_HOOK_PAIR_REMOVED)) {
931                         parent = (CamelHookPair *)&hooks->list;
932                         pair = parent->next;
933                         while (pair) {
934                                 if (pair->flags & CAMEL_HOOK_PAIR_REMOVED) {
935                                         parent->next = pair->next;
936                                         pair_free(pair);
937                                         hooks->list_length--;
938                                 } else {
939                                         parent = pair;
940                                 }
941                                 pair = parent->next;
942                         }
943                         hooks->flags &= ~CAMEL_HOOK_PAIR_REMOVED;
944                 }
945         }
946
947         camel_object_unget_hooks(obj);
948         camel_object_unref(obj);
949 }
950
951 /* get/set arg methods */
952 int camel_object_set(void *vo, CamelException *ex, ...)
953 {
954         CamelArgV args;
955         CamelObject *o = vo;
956         CamelObjectClass *klass = o->klass;
957         int ret = 0;
958
959         g_return_val_if_fail(CAMEL_IS_OBJECT(o), -1);
960
961         camel_argv_start(&args, ex);
962
963         while (camel_argv_build(&args) && ret == 0)
964                 ret = klass->setv(o, ex, &args);
965         if (ret == 0)
966                 ret = klass->setv(o, ex, &args);
967
968         camel_argv_end(&args);
969
970         return ret;
971 }
972
973 int camel_object_setv(void *vo, CamelException *ex, CamelArgV *args)
974 {
975         g_return_val_if_fail(CAMEL_IS_OBJECT(vo), -1);
976
977         return ((CamelObject *)vo)->klass->setv(vo, ex, args);
978 }
979
980 int camel_object_get(void *vo, CamelException *ex, ...)
981 {
982         CamelObject *o = vo;
983         CamelArgGetV args;
984         CamelObjectClass *klass = o->klass;
985         int ret = 0;
986
987         g_return_val_if_fail(CAMEL_IS_OBJECT(o), -1);
988
989         camel_argv_start(&args, ex);
990
991         while (camel_arggetv_build(&args) && ret == 0)
992                 ret = klass->getv(o, ex, &args);
993         if (ret == 0)
994                 ret = klass->getv(o, ex, &args);
995
996         camel_argv_end(&args);
997
998         return ret;
999 }
1000
1001 int camel_object_getv(void *vo, CamelException *ex, CamelArgGetV *args)
1002 {
1003         g_return_val_if_fail(CAMEL_IS_OBJECT(vo), -1);
1004
1005         return ((CamelObject *)vo)->klass->getv(vo, ex, args);
1006 }
1007
1008 /* free an arg object, you can only free objects 1 at a time */
1009 void camel_object_free(void *vo, guint32 tag, void *value)
1010 {
1011         g_return_if_fail(CAMEL_IS_OBJECT(vo));
1012
1013         /* We could also handle freeing of args differently
1014
1015            Add a 'const' bit to the arg type field,
1016            specifying that the object should not be freed.
1017            
1018            And, add free handlers for any pointer objects which are
1019            not const.  The free handlers would be hookpairs off of the
1020            class.
1021
1022            Then we handle the basic types OBJ,STR here, and pass PTR
1023            types to their appropriate handler, without having to pass
1024            through the invocation heirarchy of the free method.
1025
1026            This would also let us copy and do other things with args
1027            we can't do, but i can't see a use for that yet ...  */
1028
1029         ((CamelObject *)vo)->klass->free(vo, tag, value);
1030 }
1031
1032 static void
1033 object_class_dump_tree_rec(CamelType root, int depth)
1034 {
1035         char *p;
1036 #ifdef CAMEL_OBJECT_TRACK_INSTANCES
1037         struct _CamelObject *o;
1038 #endif
1039
1040         p = alloca(depth*2+1);
1041         memset(p, ' ', depth*2);
1042         p[depth*2] = 0;
1043
1044         while (root) {
1045                 CLASS_LOCK(root);
1046                 printf("%sClass: %s\n", p, root->name);
1047                 /*printf("%sVersion: %u.%u\n", p, root->version, root->revision);*/
1048                 if (root->hooks) {
1049                         CamelHookPair *pair = root->hooks;
1050
1051                         while (pair) {
1052                                 printf("%s  event '%s' prep %p\n", p, pair->name, pair->func.prep);
1053                                 pair = pair->next;
1054                         }
1055                 }
1056 #ifdef CAMEL_OBJECT_TRACK_INSTANCES
1057                 o = root->instances;
1058                 while (o) {
1059                         printf("%s instance %p [%d]\n", p, o, o->ref_count);
1060                         /* todo: should lock hooks while it scans them */
1061                         if (o->hooks) {
1062                                 CamelHookPair *pair = o->hooks->list;
1063
1064                                 while (pair) {
1065                                         printf("%s  hook '%s' func %p data %p\n", p, pair->name, pair->func.event, pair->data);
1066                                         pair = pair->next;
1067                                 }
1068                         }
1069                         o = o->next;
1070                 }
1071 #endif
1072                 CLASS_UNLOCK(root);
1073
1074                 if (root->child)
1075                         object_class_dump_tree_rec(root->child, depth+1);
1076
1077                 root = root->next;
1078         }
1079 }
1080
1081 void
1082 camel_object_class_dump_tree(CamelType root)
1083 {
1084         object_class_dump_tree_rec(root, 0);
1085 }
1086
1087 CamelObjectBag *camel_object_bag_new(GHashFunc hash, GEqualFunc equal, CamelCopyFunc keycopy, GFreeFunc keyfree)
1088 {
1089         CamelObjectBag *bag;
1090
1091         bag = g_malloc(sizeof(*bag));
1092         bag->object_table = g_hash_table_new(hash, equal);
1093         bag->copy_key = keycopy;
1094         bag->free_key = keyfree;
1095         bag->key_table = g_hash_table_new(NULL, NULL);
1096         bag->owner = 0;
1097 #ifdef ENABLE_THREADS
1098         /* init the semaphore to 1 owner, this is who has reserved the bag for adding */
1099         sem_init(&bag->reserve_sem, 0, 1);
1100 #endif
1101         return bag;
1102 }
1103
1104 static void
1105 save_object(void *key, CamelObject *o, GPtrArray *objects)
1106 {
1107         g_ptr_array_add(objects, o);
1108 }
1109
1110 static void
1111 save_key(void *key, CamelObject *o, GPtrArray *keys)
1112 {
1113         g_ptr_array_add (keys, key);
1114 }
1115
1116 void camel_object_bag_destroy(CamelObjectBag *bag)
1117 {
1118         GPtrArray *objects = g_ptr_array_new();
1119         GPtrArray *keys = g_ptr_array_new ();
1120         int i;
1121
1122         sem_getvalue(&bag->reserve_sem, &i);
1123         g_assert(i == 1);
1124
1125         g_hash_table_foreach(bag->object_table, (GHFunc)save_object, objects);
1126         g_hash_table_foreach(bag->object_table, (GHFunc)save_key, keys);
1127         for (i=0;i<objects->len;i++) {
1128                 camel_object_bag_remove(bag, objects->pdata[i]);
1129                 bag->free_key (keys->pdata[i]);
1130         }
1131         
1132         g_ptr_array_free (keys, TRUE);
1133         g_ptr_array_free(objects, TRUE);
1134         g_hash_table_destroy(bag->object_table);
1135         g_hash_table_destroy(bag->key_table);
1136 #ifdef ENABLE_THREADS
1137         sem_destroy(&bag->reserve_sem);
1138 #endif
1139         g_free(bag);
1140 }
1141
1142 void camel_object_bag_add(CamelObjectBag *bag, const void *key, void *vo)
1143 {
1144         CamelObject *o = vo;
1145         CamelHookList *hooks;
1146         CamelHookPair *pair;
1147         void *k;
1148
1149         hooks = camel_object_get_hooks(o);
1150         E_LOCK(type_lock);
1151
1152         pair = hooks->list;
1153         while (pair) {
1154                 if (pair->name == bag_name && pair->data == bag) {
1155                         E_UNLOCK(type_lock);
1156                         camel_object_unget_hooks(o);
1157                         return;
1158                 }
1159                 pair = pair->next;
1160         }
1161
1162         pair = pair_alloc();
1163         pair->name = bag_name;
1164         pair->data = bag;
1165         pair->flags = 0;
1166
1167         pair->next = hooks->list;
1168         hooks->list = pair;
1169         hooks->list_length++;
1170
1171         k = bag->copy_key(key);
1172         g_hash_table_insert(bag->object_table, k, vo);
1173         g_hash_table_insert(bag->key_table, vo, k);
1174
1175 #ifdef ENABLE_THREADS
1176         if (bag->owner == pthread_self()) {
1177                 bag->owner = 0;
1178                 sem_post(&bag->reserve_sem);
1179         }
1180 #endif
1181
1182         E_UNLOCK(type_lock);
1183         camel_object_unget_hooks(o);
1184 }
1185
1186 void *camel_object_bag_get(CamelObjectBag *bag, const void *key)
1187 {
1188         CamelObject *o;
1189
1190         E_LOCK(type_lock);
1191
1192         o = g_hash_table_lookup(bag->object_table, key);
1193         if (o) {
1194                 /* we use the same lock as the refcount */
1195                 o->ref_count++;
1196         }
1197 #ifdef ENABLE_THREADS
1198         else if (bag->owner != pthread_self()) {
1199                 E_UNLOCK(type_lock);
1200                 sem_wait(&bag->reserve_sem);
1201                 E_LOCK(type_lock);
1202                 /* re-check if it slipped in */
1203                 o = g_hash_table_lookup(bag->object_table, key);
1204                 if (o)
1205                         o->ref_count++;
1206                 /* we dont want to reserve the bag */
1207                 sem_post(&bag->reserve_sem);
1208         }
1209 #endif
1210         
1211         E_UNLOCK(type_lock);
1212
1213         return o;
1214 }
1215
1216 /* like get, but also reserve a spot for key if it isn't there */
1217 /* After calling reserve, you MUST call bag_abort or bag_add */
1218 /* Also note that currently you can only reserve a single key
1219    at any one time in a given thread */
1220 void *camel_object_bag_reserve(CamelObjectBag *bag, const void *key)
1221 {
1222         CamelObject *o;
1223
1224         E_LOCK(type_lock);
1225
1226         o = g_hash_table_lookup(bag->object_table, key);
1227         if (o) {
1228                 o->ref_count++;
1229         }
1230 #ifdef ENABLE_THREADS
1231         else {
1232                 g_assert(bag->owner != pthread_self());
1233                 E_UNLOCK(type_lock);
1234                 sem_wait(&bag->reserve_sem);
1235                 E_LOCK(type_lock);
1236                 /* incase its slipped in while we were waiting */
1237                 o = g_hash_table_lookup(bag->object_table, key);
1238                 if (o) {
1239                         o->ref_count++;
1240                         /* in which case we dont need to reserve the bag either */
1241                         sem_post(&bag->reserve_sem);
1242                 } else {
1243                         bag->owner = pthread_self();
1244                 }
1245         }
1246 #endif
1247         
1248         E_UNLOCK(type_lock);
1249
1250         return o;
1251 }
1252
1253 /* abort a reserved key */
1254 void camel_object_bag_abort(CamelObjectBag *bag, const void *key)
1255 {
1256 #ifdef ENABLE_THREADS
1257         g_assert(bag->owner == pthread_self());
1258
1259         bag->owner = 0;
1260         sem_post(&bag->reserve_sem);
1261 #endif
1262 }
1263
1264 static void
1265 save_bag(void *key, CamelObject *o, GPtrArray *list)
1266 {
1267         /* we have the refcount lock already */
1268         o->ref_count++;
1269         g_ptr_array_add(list, o);
1270 }
1271
1272 /* get a list of all objects in the bag, ref'd
1273    ignores any reserved keys */
1274 GPtrArray *camel_object_bag_list(CamelObjectBag *bag)
1275 {
1276         GPtrArray *list;
1277
1278         list = g_ptr_array_new();
1279
1280         E_LOCK(type_lock);
1281         g_hash_table_foreach(bag->object_table, (GHFunc)save_bag, list);
1282         E_UNLOCK(type_lock);
1283
1284         return list;
1285 }
1286
1287 /* if bag is NULL, remove all bags from object */
1288 static void camel_object_bag_remove_unlocked(CamelObjectBag *inbag, CamelObject *o, CamelHookList *hooks)
1289 {
1290         CamelHookPair *pair, *parent;
1291         void *oldkey;
1292         CamelObjectBag *bag;
1293
1294         parent = (CamelHookPair *)&hooks->list;
1295         pair = parent->next;
1296         while (pair) {
1297                 if (pair->name == bag_name
1298                     && (inbag == NULL || inbag == pair->data)) {
1299                         bag = pair->data;
1300                         /* lookup object in table? */
1301                         oldkey = g_hash_table_lookup(bag->key_table, o);
1302                         if (oldkey) {
1303                                 g_hash_table_remove(bag->key_table, o);
1304                                 g_hash_table_remove(bag->object_table, oldkey);
1305                                 bag->free_key(oldkey);
1306                         }
1307                         parent->next = pair->next;
1308                         pair_free(pair);
1309                         hooks->list_length--;
1310                 } else {
1311                         parent = pair;
1312                 }
1313                 pair = parent->next;
1314         }
1315 }
1316
1317 void camel_object_bag_remove(CamelObjectBag *inbag, void *vo)
1318 {
1319         CamelObject *o = vo;
1320         CamelHookList *hooks;
1321
1322         if (o->hooks == NULL)
1323                 return;
1324
1325         hooks = camel_object_get_hooks(o);
1326         E_LOCK(type_lock);
1327
1328         camel_object_bag_remove_unlocked(inbag, o, hooks);
1329                 
1330         E_UNLOCK(type_lock);
1331         camel_object_unget_hooks(o);
1332 }