1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 #include <cghdr.h>
15 
16 static char DRName[] = "_AG_pending";
17 
18 typedef struct symlist_s {
19     Agsym_t *sym;
20     struct symlist_s *link;
21 } symlist_t;
22 
23 /* this record describes one pending callback on one object */
24 typedef struct {
25     Dtlink_t link;
26     IDTYPE key;		/* universal key for main or sub-object */
27     Agraph_t *g;
28     Agobj_t *obj;
29     symlist_t *symlist;		/* attributes involved */
30 } pending_cb_t;
31 
32 typedef struct {
33     Agrec_t h;
34     struct {
35 	Dict_t *g, *n, *e;
36     } ins, mod, del;
37 } pendingset_t;
38 
free_symlist(pending_cb_t * pcb)39 static void free_symlist(pending_cb_t * pcb)
40 {
41     symlist_t *s, *t;
42 
43     for (s = pcb->symlist; s; s = t) {
44 	t = s->link;
45 	agfree(pcb->g, s);
46     }
47 }
48 
freef(Dict_t * dict,void * ptr,Dtdisc_t * disc)49 static void freef(Dict_t * dict, void *ptr, Dtdisc_t * disc)
50 {
51     pending_cb_t *pcb;
52 
53     NOTUSED(dict);
54     NOTUSED(disc);
55     pcb = ptr;
56     free_symlist(pcb);
57     agfree(pcb->g, pcb);
58 }
59 
60 static Dtdisc_t Disc = {
61     offsetof(pending_cb_t, key),	/* sort by 'key' */
62     sizeof(uint64_t),
63     0,				/* link offset */
64     NIL(Dtmake_f),
65     freef,
66     NIL(Dtcompar_f),
67     NIL(Dthash_f)
68 };
69 
dictof(pendingset_t * ds,Agobj_t * obj,int kind)70 static Dict_t *dictof(pendingset_t * ds, Agobj_t * obj, int kind)
71 {
72     Dict_t **dict_ref = NIL(Dict_t **);
73 
74     dict_ref = 0;
75     switch (AGTYPE(obj)) {
76     case AGRAPH:
77 	switch (kind) {
78 	case CB_INITIALIZE:
79 	    dict_ref = &(ds->ins.g);
80 	    break;
81 	case CB_UPDATE:
82 	    dict_ref = &(ds->mod.g);
83 	    break;
84 	case CB_DELETION:
85 	    dict_ref = &(ds->del.g);
86 	    break;
87 	default:
88 	    break;
89 	}
90 	break;
91     case AGNODE:
92 	switch (kind) {
93 	case CB_INITIALIZE:
94 	    dict_ref = &(ds->ins.n);
95 	    break;
96 	case CB_UPDATE:
97 	    dict_ref = &(ds->mod.n);
98 	    break;
99 	case CB_DELETION:
100 	    dict_ref = &(ds->del.n);
101 	    break;
102 	default:
103 	    break;
104 	}
105 	break;
106     case AGEDGE:
107 	switch (kind) {
108 	case CB_INITIALIZE:
109 	    dict_ref = &(ds->ins.e);
110 	    break;
111 	case CB_UPDATE:
112 	    dict_ref = &(ds->mod.e);
113 	    break;
114 	case CB_DELETION:
115 	    dict_ref = &(ds->del.e);
116 	    break;
117 	default:
118 	    break;
119 	}
120 	break;
121     default:
122 	break;
123     }
124 
125     if (dict_ref == 0)
126 	agerr(AGERR, "pend dictof a bad object");
127     if (*dict_ref == NIL(Dict_t *))
128 	*dict_ref = agdtopen(agraphof(obj), &Disc, Dttree);
129     return *dict_ref;
130 }
131 
genkey(Agobj_t * obj)132 static IDTYPE genkey(Agobj_t * obj)
133 {
134     return obj->tag.id;
135 }
136 
lookup(Dict_t * dict,Agobj_t * obj)137 static pending_cb_t *lookup(Dict_t * dict, Agobj_t * obj)
138 {
139     pending_cb_t key, *rv;
140 
141     key.key = genkey(obj);
142     rv = (pending_cb_t *) dtsearch(dict, &key);
143     return rv;
144 }
145 
record_sym(Agobj_t * obj,pending_cb_t * handle,Agsym_t * optsym)146 static void record_sym(Agobj_t * obj, pending_cb_t * handle,
147 		       Agsym_t * optsym)
148 {
149     symlist_t *sym, *nsym, *psym;
150 
151     psym = NIL(symlist_t *);
152     for (sym = handle->symlist; sym; psym = sym, sym = sym->link) {
153 	if (sym->sym == optsym)
154 	    break;
155 	if (sym == NIL(symlist_t *)) {
156 	    nsym = agalloc(agraphof(obj), sizeof(symlist_t));
157 	    nsym->sym = optsym;
158 	    if (psym)
159 		psym->link = nsym;
160 	    else
161 		handle->symlist = nsym;
162 	}
163 	/* else we already have a callback registered */
164     }
165 }
166 
insert(Dict_t * dict,Agraph_t * g,Agobj_t * obj,Agsym_t * optsym)167 static pending_cb_t *insert(Dict_t * dict, Agraph_t * g, Agobj_t * obj,
168 			    Agsym_t * optsym)
169 {
170     pending_cb_t *handle;
171     handle = agalloc(agraphof(obj), sizeof(pending_cb_t));
172     handle->obj = obj;
173     handle->key = genkey(obj);
174     handle->g = g;
175     if (optsym) {
176 	handle->symlist =
177 	    (symlist_t *) agalloc(handle->g, sizeof(symlist_t));
178 	handle->symlist->sym = optsym;
179     }
180     dtinsert(dict, handle);
181     return handle;
182 }
183 
purge(Dict_t * dict,Agobj_t * obj)184 static void purge(Dict_t * dict, Agobj_t * obj)
185 {
186     pending_cb_t *handle;
187 
188     if ((handle = lookup(dict, obj))) {
189 	dtdelete(dict, handle);
190     }
191 }
192 
agrecord_callback(Agraph_t * g,Agobj_t * obj,int kind,Agsym_t * optsym)193 void agrecord_callback(Agraph_t * g, Agobj_t * obj, int kind,
194 		       Agsym_t * optsym)
195 {
196     pendingset_t *pending;
197     Dict_t *dict;
198     pending_cb_t *handle;
199 
200     pending =
201 	(pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t), FALSE);
202 
203     switch (kind) {
204     case CB_INITIALIZE:
205 	assert(lookup(dictof(pending, obj, CB_UPDATE), obj) == 0);
206 	assert(lookup(dictof(pending, obj, CB_DELETION), obj) == 0);
207 	dict = dictof(pending, obj, CB_INITIALIZE);
208 	handle = lookup(dict, obj);
209 	if (handle == 0)
210 	    handle = insert(dict, g, obj, optsym);
211 	break;
212     case CB_UPDATE:
213 	if (lookup(dictof(pending, obj, CB_INITIALIZE), obj))
214 	    break;
215 	if (lookup(dictof(pending, obj, CB_DELETION), obj))
216 	    break;
217 	dict = dictof(pending, obj, CB_UPDATE);
218 	handle = lookup(dict, obj);
219 	if (handle == 0)
220 	    handle = insert(dict, g, obj, optsym);
221 	record_sym(obj, handle, optsym);
222 	break;
223     case CB_DELETION:
224 	purge(dictof(pending, obj, CB_INITIALIZE), obj);
225 	purge(dictof(pending, obj, CB_UPDATE), obj);
226 	dict = dictof(pending, obj, CB_DELETION);
227 	handle = lookup(dict, obj);
228 	if (handle == 0)
229 	    handle = insert(dict, g, obj, optsym);
230 	break;
231     default:
232 	agerr(AGERR,"agrecord_callback of a bad object");
233     }
234 }
235 
cb(Dict_t * dict,int callback_kind)236 static void cb(Dict_t * dict, int callback_kind)
237 {
238     pending_cb_t *pcb;
239     Agraph_t *g;
240     symlist_t *psym;
241     Agcbstack_t *stack;
242 
243     if (dict)
244 	while ((pcb = (pending_cb_t *) dtfirst(dict))) {
245 	    g = pcb->g;
246 	    stack = g->clos->cb;
247 	    switch (callback_kind) {
248 	    case CB_INITIALIZE:
249 		aginitcb(g, pcb->obj, stack);
250 		break;
251 	    case CB_UPDATE:
252 		for (psym = pcb->symlist; psym; psym = psym->link)
253 		    agupdcb(g, pcb->obj, psym->sym, stack);
254 		break;
255 	    case CB_DELETION:
256 		agdelcb(g, pcb->obj, stack);
257 		break;
258 	    }
259 	    dtdelete(dict, pcb);
260 	}
261 }
262 
agrelease_callbacks(Agraph_t * g)263 static void agrelease_callbacks(Agraph_t * g)
264 {
265     pendingset_t *pending;
266     if (NOT(g->clos->callbacks_enabled)) {
267 	g->clos->callbacks_enabled = TRUE;
268 	pending =
269 	    (pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t),
270 				       FALSE);
271 	/* this destroys objects in the opposite of their order of creation */
272 	cb(pending->ins.g, CB_INITIALIZE);
273 	cb(pending->ins.n, CB_INITIALIZE);
274 	cb(pending->ins.e, CB_INITIALIZE);
275 
276 	cb(pending->mod.g, CB_UPDATE);
277 	cb(pending->mod.n, CB_UPDATE);
278 	cb(pending->mod.e, CB_UPDATE);
279 
280 	cb(pending->del.e, CB_DELETION);
281 	cb(pending->del.n, CB_DELETION);
282 	cb(pending->del.g, CB_DELETION);
283     }
284 }
285 
agcallbacks(Agraph_t * g,int flag)286 int agcallbacks(Agraph_t * g, int flag)
287 {
288     if (flag && NOT(g->clos->callbacks_enabled))
289 	agrelease_callbacks(g);
290     if (g->clos->callbacks_enabled) {
291 	g->clos->callbacks_enabled = flag;
292 	return TRUE;
293     }
294     g->clos->callbacks_enabled = flag;
295     return FALSE;
296 }
297