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/*
17 * dynamic attributes
18 */
19
20/* to create a graph's data dictionary */
21
22#define MINATTR 4 /* minimum allocation */
23
24static void freesym(Dict_t * d, void * obj, Dtdisc_t * disc);
25
26Dtdisc_t AgDataDictDisc = {
27 (int) offsetof(Agsym_t, name), /* use symbol name as key */
28 -1,
29 (int) offsetof(Agsym_t, link),
30 NIL(Dtmake_f),
31 freesym,
32 NIL(Dtcompar_f),
33 NIL(Dthash_f)
34};
35
36static char DataDictName[] = "_AG_datadict";
37static void init_all_attrs(Agraph_t * g);
38static Agdesc_t ProtoDesc = { 1, 0, 1, 0, 1, 1 };
39static Agraph_t *ProtoGraph;
40
41Agdatadict_t *agdatadict(Agraph_t * g, int cflag)
42{
43 Agdatadict_t *rv;
44 rv = (Agdatadict_t *) aggetrec(g, DataDictName, FALSE);
45 if (rv || !cflag)
46 return rv;
47 init_all_attrs(g);
48 rv = (Agdatadict_t *) aggetrec(g, DataDictName, FALSE);
49 return rv;
50}
51
52Dict_t *agdictof(Agraph_t * g, int kind)
53{
54 Agdatadict_t *dd;
55 Dict_t *dict;
56
57 dd = agdatadict(g, FALSE);
58 if (dd)
59 switch (kind) {
60 case AGRAPH:
61 dict = dd->dict.g;
62 break;
63 case AGNODE:
64 dict = dd->dict.n;
65 break;
66 case AGINEDGE:
67 case AGOUTEDGE:
68 dict = dd->dict.e;
69 break;
70 default:
71 agerr(AGERR,"agdictof: unknown kind %d\n", kind);
72 dict = NIL(Dict_t *);
73 break;
74 } else
75 dict = NIL(Dict_t *);
76 return dict;
77}
78
79Agsym_t *agnewsym(Agraph_t * g, char *name, char *value, int id, int kind)
80{
81 Agsym_t *sym;
82 sym = agalloc(g, sizeof(Agsym_t));
83 sym->kind = kind;
84 sym->name = agstrdup(g, name);
85 sym->defval = agstrdup(g, value);
86 sym->id = id;
87 return sym;
88}
89
90static void agcopydict(Dict_t * src, Dict_t * dest, Agraph_t * g, int kind)
91{
92 Agsym_t *sym, *newsym;
93
94 assert(dtsize(dest) == 0);
95 for (sym = (Agsym_t *) dtfirst(src); sym;
96 sym = (Agsym_t *) dtnext(src, sym)) {
97 newsym = agnewsym(g, sym->name, sym->defval, sym->id, kind);
98 newsym->print = sym->print;
99 newsym->fixed = sym->fixed;
100 dtinsert(dest, newsym);
101 }
102}
103
104static Agdatadict_t *agmakedatadict(Agraph_t * g)
105{
106 Agraph_t *par;
107 Agdatadict_t *parent_dd, *dd;
108
109 dd = (Agdatadict_t *) agbindrec(g, DataDictName, sizeof(Agdatadict_t),
110 FALSE);
111 dd->dict.n = agdtopen(g, &AgDataDictDisc, Dttree);
112 dd->dict.e = agdtopen(g, &AgDataDictDisc, Dttree);
113 dd->dict.g = agdtopen(g, &AgDataDictDisc, Dttree);
114 if ((par = agparent(g))) {
115 parent_dd = agdatadict(par, FALSE);
116 assert(dd != parent_dd);
117 dtview(dd->dict.n, parent_dd->dict.n);
118 dtview(dd->dict.e, parent_dd->dict.e);
119 dtview(dd->dict.g, parent_dd->dict.g);
120 } else {
121 if (ProtoGraph && (g != ProtoGraph)) {
122 /* it's not ok to dtview here for several reasons. the proto
123 graph could change, and the sym indices don't match */
124 parent_dd = agdatadict(ProtoGraph, FALSE);
125 agcopydict(parent_dd->dict.n, dd->dict.n, g, AGNODE);
126 agcopydict(parent_dd->dict.e, dd->dict.e, g, AGEDGE);
127 agcopydict(parent_dd->dict.g, dd->dict.g, g, AGRAPH);
128 }
129 }
130 return dd;
131}
132
133/* look up an attribute with possible viewpathing */
134Agsym_t *agdictsym(Dict_t * dict, char *name)
135{
136 Agsym_t key;
137 key.name = (char *) name;
138 return (Agsym_t *) dtsearch(dict, &key);
139}
140
141/* look up attribute in local dictionary with no view pathing */
142Agsym_t *aglocaldictsym(Dict_t * dict, char *name)
143{
144 Agsym_t *rv;
145 Dict_t *view;
146
147 view = dtview(dict, NIL(Dict_t *));
148 rv = agdictsym(dict, name);
149 dtview(dict, view);
150 return rv;
151}
152
153Agsym_t *agattrsym(void *obj, char *name)
154{
155 Agattr_t *data;
156 Agsym_t *rv;
157 char *arg = name;
158
159 data = agattrrec((Agobj_t *) obj);
160 if (data)
161 rv = agdictsym(data->dict, arg);
162 else
163 rv = NILsym;
164 return rv;
165}
166
167/* to create a graph's, node's edge's string attributes */
168
169char *AgDataRecName = "_AG_strdata";
170
171static int topdictsize(Agobj_t * obj)
172{
173 Dict_t *d;
174
175 d = agdictof(agroot(agraphof(obj)), AGTYPE(obj));
176 return d ? dtsize(d) : 0;
177}
178
179/* g can be either the enclosing graph, or ProtoGraph */
180static Agrec_t *agmakeattrs(Agraph_t * context, void *obj)
181{
182 int sz;
183 Agattr_t *rec;
184 Agsym_t *sym;
185 Dict_t *datadict;
186
187 rec = agbindrec(obj, AgDataRecName, sizeof(Agattr_t), FALSE);
188 datadict = agdictof(context, AGTYPE(obj));
189 assert(datadict);
190 if (rec->dict == NIL(Dict_t *)) {
191 rec->dict = agdictof(agroot(context), AGTYPE(obj));
192 /* don't malloc(0) */
193 sz = topdictsize(obj);
194 if (sz < MINATTR)
195 sz = MINATTR;
196 rec->str = agalloc(agraphof(obj), (size_t) sz * sizeof(char *));
197 /* doesn't call agxset() so no obj-modified callbacks occur */
198 for (sym = (Agsym_t *) dtfirst(datadict); sym;
199 sym = (Agsym_t *) dtnext(datadict, sym))
200 rec->str[sym->id] = agstrdup(agraphof(obj), sym->defval);
201 } else {
202 assert(rec->dict == datadict);
203 }
204 return (Agrec_t *) rec;
205}
206
207static void freeattr(Agobj_t * obj, Agattr_t * attr)
208{
209 int i, sz;
210 Agraph_t *g;
211
212 g = agraphof(obj);
213 sz = topdictsize(obj);
214 for (i = 0; i < sz; i++)
215 agstrfree(g, attr->str[i]);
216 agfree(g, attr->str);
217}
218
219static void freesym(Dict_t * d, void * obj, Dtdisc_t * disc)
220{
221 Agsym_t *sym;
222
223 NOTUSED(d);
224 sym = (Agsym_t *) obj;
225 NOTUSED(disc);
226 agstrfree(Ag_G_global, sym->name);
227 agstrfree(Ag_G_global, sym->defval);
228 agfree(Ag_G_global, sym);
229}
230
231Agattr_t *agattrrec(void *obj)
232{
233 return (Agattr_t *) aggetrec(obj, AgDataRecName, FALSE);
234}
235
236
237static void addattr(Agraph_t * g, Agobj_t * obj, Agsym_t * sym)
238{
239 Agattr_t *attr;
240
241 attr = (Agattr_t *) agattrrec(obj);
242 assert(attr != NIL(Agattr_t *));
243 if (sym->id >= MINATTR)
244 attr->str = (char **) AGDISC(g, mem)->resize(AGCLOS(g, mem),
245 attr->str,
246 sym->id *
247 sizeof(char *),
248 (sym->id +
249 1) * sizeof(char *));
250 attr->str[sym->id] = agstrdup(g, sym->defval);
251 /* agmethod_upd(g,obj,sym); JCE and GN didn't like this. */
252}
253
254
255static Agsym_t *setattr(Agraph_t * g, int kind, char *name, char *value)
256{
257 Agdatadict_t *dd;
258 Dict_t *ldict, *rdict;
259 Agsym_t *lsym, *psym, *rsym, *rv;
260 Agraph_t *root;
261 Agnode_t *n;
262 Agedge_t *e;
263
264 assert(value);
265 root = agroot(g);
266 dd = agdatadict(g, TRUE); /* force initialization of string attributes */
267 ldict = agdictof(g, kind);
268 lsym = aglocaldictsym(ldict, name);
269 if (lsym) { /* update old local definition */
270 agstrfree(g, lsym->defval);
271 lsym->defval = agstrdup(g, value);
272 rv = lsym;
273 } else {
274 psym = agdictsym(ldict, name); /* search with viewpath up to root */
275 if (psym) { /* new local definition */
276 lsym = agnewsym(g, name, value, psym->id, kind);
277 dtinsert(ldict, lsym);
278 rv = lsym;
279 } else { /* new global definition */
280 rdict = agdictof(root, kind);
281 rsym = agnewsym(g, name, value, dtsize(rdict), kind);
282 dtinsert(rdict, rsym);
283 switch (kind) {
284 case AGRAPH:
285 agapply(root, (Agobj_t *) root, (agobjfn_t) addattr,
286 rsym, TRUE);
287 break;
288 case AGNODE:
289 for (n = agfstnode(root); n; n = agnxtnode(root, n))
290 addattr(g, (Agobj_t *) n, rsym);
291 break;
292 case AGINEDGE:
293 case AGOUTEDGE:
294 for (n = agfstnode(root); n; n = agnxtnode(root, n))
295 for (e = agfstout(root, n); e; e = agnxtout(root, e))
296 addattr(g, (Agobj_t *) e, rsym);
297 break;
298 }
299 rv = rsym;
300 }
301 }
302 if (rv && (kind == AGRAPH))
303 agxset(g, rv, value);
304 agmethod_upd(g, g, rv); /* JCE and GN wanted this */
305 return rv;
306}
307
308static Agsym_t *getattr(Agraph_t * g, int kind, char *name)
309{
310 Agsym_t *rv = 0;
311 Dict_t *dict;
312 dict = agdictof(g, kind);
313 if (dict)
314 rv = agdictsym(dict, name); /* viewpath up to root */
315 return rv;
316}
317
318/*
319 * create or update an existing attribute and return its descriptor.
320 * if the new value is NIL(char*), this is only a search, no update.
321 * when a new attribute is created, existing graphs/nodes/edges
322 * receive its default value.
323 */
324Agsym_t *agattr(Agraph_t * g, int kind, char *name, char *value)
325{
326 Agsym_t *rv;
327
328 if (g == 0) {
329 if (ProtoGraph == 0)
330 ProtoGraph = agopen(0, ProtoDesc, 0);
331 g = ProtoGraph;
332 }
333 if (value)
334 rv = setattr(g, kind, name, value);
335 else
336 rv = getattr(g, kind, name);
337 return rv;
338}
339
340Agsym_t *agnxtattr(Agraph_t * g, int kind, Agsym_t * attr)
341{
342 Dict_t *d;
343 Agsym_t *rv;
344
345 if ((d = agdictof(g, kind))) {
346 if (attr)
347 rv = (Agsym_t *) dtnext(d, attr);
348 else
349 rv = (Agsym_t *) dtfirst(d);
350 } else
351 rv = 0;
352 return rv;
353}
354
355/* Create or delete attributes associated with an object */
356
357void agraphattr_init(Agraph_t * g)
358{
359 /* Agdatadict_t *dd; */
360 /* Agrec_t *attr; */
361 Agraph_t *context;
362
363 g->desc.has_attrs = 1;
364 /* dd = */ agmakedatadict(g);
365 if (!(context = agparent(g)))
366 context = g;
367 /* attr = */ agmakeattrs(context, g);
368}
369
370int agraphattr_delete(Agraph_t * g)
371{
372 Agdatadict_t *dd;
373 Agattr_t *attr;
374
375 Ag_G_global = g;
376 if ((attr = agattrrec(g))) {
377 freeattr((Agobj_t *) g, attr);
378 agdelrec(g, attr->h.name);
379 }
380
381 if ((dd = agdatadict(g, FALSE))) {
382 if (agdtclose(g, dd->dict.n)) return 1;
383 if (agdtclose(g, dd->dict.e)) return 1;
384 if (agdtclose(g, dd->dict.g)) return 1;
385 agdelrec(g, dd->h.name);
386 }
387 return 0;
388}
389
390void agnodeattr_init(Agraph_t * g, Agnode_t * n)
391{
392 Agattr_t *data;
393
394 data = agattrrec(n);
395 if ((!data) || (!data->dict))
396 (void) agmakeattrs(g, n);
397}
398
399void agnodeattr_delete(Agnode_t * n)
400{
401 Agattr_t *rec;
402
403 if ((rec = agattrrec(n))) {
404 freeattr((Agobj_t *) n, rec);
405 agdelrec(n, AgDataRecName);
406 }
407}
408
409void agedgeattr_init(Agraph_t * g, Agedge_t * e)
410{
411 Agattr_t *data;
412
413 data = agattrrec(e);
414 if ((!data) || (!data->dict))
415 (void) agmakeattrs(g, e);
416}
417
418void agedgeattr_delete(Agedge_t * e)
419{
420 Agattr_t *rec;
421
422 if ((rec = agattrrec(e))) {
423 freeattr((Agobj_t *) e, rec);
424 agdelrec(e, AgDataRecName);
425 }
426}
427
428char *agget(void *obj, char *name)
429{
430 Agsym_t *sym;
431 Agattr_t *data;
432 char *rv;
433
434 sym = agattrsym(obj, name);
435 if (sym == NILsym)
436 rv = 0; /* note was "", but this provides more info */
437 else {
438 data = agattrrec((Agobj_t *) obj);
439 rv = (char *) (data->str[sym->id]);
440 }
441 return rv;
442}
443
444char *agxget(void *obj, Agsym_t * sym)
445{
446 Agattr_t *data;
447 char *rv;
448
449 data = agattrrec((Agobj_t *) obj);
450 assert((sym->id >= 0) && (sym->id < topdictsize(obj)));
451 rv = (char *) (data->str[sym->id]);
452 return rv;
453}
454
455int agset(void *obj, char *name, char *value)
456{
457 Agsym_t *sym;
458 int rv;
459
460 sym = agattrsym(obj, name);
461 if (sym == NILsym)
462 rv = FAILURE;
463 else
464 rv = agxset(obj, sym, value);
465 return rv;
466}
467
468int agxset(void *obj, Agsym_t * sym, char *value)
469{
470 Agraph_t *g;
471 Agobj_t *hdr;
472 Agattr_t *data;
473 Agsym_t *lsym;
474
475 g = agraphof(obj);
476 hdr = (Agobj_t *) obj;
477 data = agattrrec(hdr);
478 assert((sym->id >= 0) && (sym->id < topdictsize(obj)));
479 agstrfree(g, data->str[sym->id]);
480 data->str[sym->id] = agstrdup(g, value);
481 if (hdr->tag.objtype == AGRAPH) {
482 /* also update dict default */
483 Dict_t *dict;
484 dict = agdatadict(g, FALSE)->dict.g;
485 if ((lsym = aglocaldictsym(dict, sym->name))) {
486 agstrfree(g, lsym->defval);
487 lsym->defval = agstrdup(g, value);
488 } else {
489 lsym = agnewsym(g, sym->name, value, sym->id, AGTYPE(hdr));
490 dtinsert(dict, lsym);
491 }
492 }
493 agmethod_upd(g, obj, sym);
494 return SUCCESS;
495}
496
497int agsafeset(void *obj, char *name, char *value, char *def)
498{
499 Agsym_t *a;
500
501 a = agattr(agraphof(obj), AGTYPE(obj), name, 0);
502 if (!a)
503 a = agattr(agraphof(obj), AGTYPE(obj), name, def);
504 return agxset(obj, a, value);
505}
506
507
508/*
509 * attach attributes to the already created graph objs.
510 * presumably they were already initialized, so we don't invoke
511 * any of the old methods.
512 */
513static void init_all_attrs(Agraph_t * g)
514{
515 Agraph_t *root;
516 Agnode_t *n;
517 Agedge_t *e;
518
519 root = agroot(g);
520 agapply(root, (Agobj_t *) root, (agobjfn_t) agraphattr_init,
521 NIL(Agdisc_t *), TRUE);
522 for (n = agfstnode(root); n; n = agnxtnode(root, n)) {
523 agnodeattr_init(g, n);
524 for (e = agfstout(root, n); e; e = agnxtout(root, e)) {
525 agedgeattr_init(g, e);
526 }
527 }
528}
529
530/* agcopyattr:
531 * Assumes attributes have already been declared.
532 * Do not copy key attribute for edges, as this must be distinct.
533 * Returns non-zero on failure or if objects have different type.
534 */
535int agcopyattr(void *oldobj, void *newobj)
536{
537 Agraph_t *g;
538 Agsym_t *sym;
539 Agsym_t *newsym;
540 char* val;
541 char* nval;
542 int r = 1;
543
544 g = agraphof(oldobj);
545 if (AGTYPE(oldobj) != AGTYPE(newobj))
546 return 1;
547 sym = 0;
548 while ((sym = agnxtattr(g, AGTYPE(oldobj), sym))) {
549 newsym = agattrsym(newobj, sym->name);
550 if (!newsym)
551 return 1;
552 val = agxget(oldobj, sym);
553 r = agxset(newobj, newsym, val);
554 /* FIX(?): Each graph has its own string cache, so a whole new refstr is possibly
555 * allocated. If the original was an html string, make sure the new one is as well.
556 * If cgraph goes to single string table, this can be removed.
557 */
558 if (aghtmlstr (val)) {
559 nval = agxget (newobj, newsym);
560 agmarkhtmlstr (nval);
561 }
562 }
563 return r;
564}
565