1 /*
2 Copyright (c) 1991-2005 Thomas T. Wetmore IV
3 "The MIT license"
4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 */
8
9 #include "llstdlib.h"
10 #include "gedcom.h"
11 #include "gedcomi.h"
12 #include "vtable.h"
13 #include "cache.h"
14 #include "lloptions.h"
15
16
17 /*==============================
18 * RECORD -- in-memory representation of GEDCOM record
19 *============================*/
20 struct tag_record { /* RECORD */
21 /* a LIST is an OBJECT */
22 struct tag_vtable * vtable; /* generic object table (see vtable.h) */
23 INT refcnt; /* reference counted object */
24 NODE rec_top; /* for non-cache records */
25 NKEY rec_nkey;
26 CACHEEL rec_cel; /* cache wrapper */
27 };
28
29 /*********************************************
30 * local function prototypes
31 *********************************************/
32
33 static RECORD alloc_new_record(void);
34 static void free_rec(RECORD rec);
35 static BOOLEAN is_record_loaded (RECORD rec);
36 static void record_destructor(VTABLE *obj);
37
38 /*********************************************
39 * local variables
40 *********************************************/
41
42 static struct tag_vtable vtable_for_record = {
43 VTABLE_MAGIC
44 , "record"
45 , &record_destructor
46 , &refcountable_isref
47 , &refcountable_addref
48 , &refcountable_release
49 , 0 /* copy_fnc */
50 , &generic_get_type_name
51 };
52 static int f_nrecs=0;
53
54 /*===================================
55 * alloc_new_record -- record allocator
56 * perhaps should use special allocator like nodes
57 * returns addref'd record
58 *=================================*/
59 static RECORD
alloc_new_record(void)60 alloc_new_record (void)
61 {
62 RECORD rec;
63 ++f_nrecs;
64 rec = (RECORD)stdalloc(sizeof(*rec));
65 memset(rec, 0, sizeof(*rec));
66 /* these must be filled in by caller */
67 rec->vtable = &vtable_for_record;
68 strcpy(rec->rec_nkey.key, "");
69 rec->rec_nkey.keynum = 0;
70 rec->rec_nkey.ntype = 0;
71 ++rec->refcnt;
72 return rec;
73 }
74 /*===================================
75 * free_rec -- record deallocator
76 * Works for both free records (not in cache, have own node tree)
77 * and bound records (in cache, point to cache element)
78 * Created: 2000/12/30, Perry Rapp
79 *=================================*/
80 static void
free_rec(RECORD rec)81 free_rec (RECORD rec)
82 {
83 --f_nrecs;
84 if (rec->rec_cel) {
85 /* cached record */
86 /* cel memory belongs to cache, but we must tell it
87 that we're dying, so it doesn't point to us anymore */
88 cel_remove_record(rec->rec_cel, rec);
89 rec->rec_cel = 0; /* cel memory belongs to cache */
90 }
91 if (rec->rec_top) {
92 /* free node tree */
93 free_nodes(rec->rec_top);
94 rec->rec_top = 0;
95 }
96 strcpy(rec->rec_nkey.key, "");
97 stdfree(rec);
98 }
99 /*==============================================
100 * nztop -- Return first NODE of a RECORD
101 * handle NULL input
102 *============================================*/
103 NODE
nztop(RECORD rec)104 nztop (RECORD rec)
105 {
106 if (!rec) return 0;
107 if (rec->rec_top) {
108 /* This is only for records not in the cache */
109 ASSERT(!rec->rec_cel);
110 return rec->rec_top;
111 }
112 if (!is_record_loaded(rec)) {
113 /* Presumably we're out-of-date because our record fell out of cache */
114 /* Anyway, load via cache (actually just point to cache data) */
115 CACHEEL cel = key_to_unknown_cacheel(rec->rec_nkey.key);
116 rec->rec_cel = cel;
117 }
118 return is_cel_loaded(rec->rec_cel);
119 }
120 /*==============================================
121 * nzkey -- Return key of record
122 * handle NULL input
123 * eg, "I85" for a person I85
124 *============================================*/
125 CNSTRING
nzkey(RECORD rec)126 nzkey (RECORD rec)
127 {
128 if (!rec) return 0;
129 return rec->rec_nkey.key;
130 }
131 /*==============================================
132 * nzkeynum -- Return record number of record
133 * handle NULL input
134 * eg, 85 for a person I85
135 *============================================*/
136 INT
nzkeynum(RECORD rec)137 nzkeynum (RECORD rec)
138 {
139 if (!rec) return 0;
140 return rec->rec_nkey.keynum;
141 }
142 /*==============================================
143 * nztype -- Return type number (char) of record
144 * handle NULL input
145 * eg, 'I' for a person I85
146 *============================================*/
147 char
nztype(RECORD rec)148 nztype (RECORD rec)
149 {
150 if (!rec) return 0;
151 return rec->rec_nkey.ntype;
152 }
153 /*==============================================
154 * nzcel -- Return cache element associated with record
155 * handle NULL input
156 * does not load cache if not associated
157 *============================================*/
158 CACHEEL
nzcel(RECORD rec)159 nzcel (RECORD rec)
160 {
161 if (!rec) return 0;
162 return rec->rec_cel;
163 }
164 /*==============================================
165 * record_set_cel -- Assign cache element to record
166 * both inputs must be valid (non-null)
167 *============================================*/
168 void
record_set_cel(RECORD rec,CACHEEL cel)169 record_set_cel (RECORD rec, CACHEEL cel)
170 {
171 ASSERT(rec);
172 ASSERT(cel);
173 rec->rec_top = 0;
174 rec->rec_cel = cel;
175 }
176 /*==============================================
177 * record_remove_cel -- cache element is being cleared
178 * both inputs must be valid (non-null)
179 *============================================*/
180 void
record_remove_cel(RECORD rec,CACHEEL cel)181 record_remove_cel (RECORD rec, CACHEEL cel)
182 {
183 ASSERT(rec);
184 ASSERT(cel);
185 ASSERT(rec->rec_cel == cel);
186 rec->rec_cel = 0;
187 }
188 /*===================================
189 * make_new_record_with_info -- creates new record
190 * with key and cacheel set
191 * returns addref'd record
192 *=================================*/
193 RECORD
make_new_record_with_info(CNSTRING key,CACHEEL cel)194 make_new_record_with_info (CNSTRING key, CACHEEL cel)
195 {
196 RECORD rec = alloc_new_record();
197 set_record_key_info(rec, key);
198 record_set_cel(rec, cel);
199 return rec;
200 }
201 /*===================================
202 * set_record_key_info -- put key info into record
203 * Also set the xref of the root node correctly
204 *=================================*/
205 void
set_record_key_info(RECORD rec,CNSTRING key)206 set_record_key_info (RECORD rec, CNSTRING key)
207 {
208 char xref[12];
209 NODE node=0;
210 INT keynum = atoi(key+1);
211 char ntype = key[0];
212 sprintf(xref, "@%s@", key);
213 strcpy(rec->rec_nkey.key, key);
214 rec->rec_nkey.keynum = keynum;
215 rec->rec_nkey.ntype = ntype;
216 if ((node = rec->rec_top) != 0) {
217 if (!nxref(node) || !eqstr(nxref(node), xref)) {
218 if (nxref(node)) stdfree(nxref(node));
219 nxref(node) = strsave(xref);
220 }
221 }
222 }
223 /*==============================================
224 * is_record_loaded -- Check if record has its node tree
225 *============================================*/
226 static BOOLEAN
is_record_loaded(RECORD rec)227 is_record_loaded (RECORD rec)
228 {
229 STRING xref=0, temp=0;
230 NODE root=0;
231
232 /* does the record have a nodetree ? */
233 if (!rec || !rec->rec_cel)
234 return FALSE;
235 root = is_cel_loaded(rec->rec_cel);
236 if (!root)
237 return FALSE;
238
239 /* Now, is it the correct nodetree ? */
240
241 temp = rec->rec_nkey.key; /* eg, "I50" */
242
243 xref = nxref(root); /* eg, "@I50@" */
244 ASSERT(xref[0] == '@');
245
246 /* now xref="@I50@" and temp="I50" */
247 ++xref;
248
249 /* now xref="I50@" and temp="I50" */
250
251 while (1) {
252 /* distinguish I50@ vs I50 from I50@ vs I500 */
253 if (*xref == '@') return (*temp == 0);
254 /* distinguish I50@ vs I50 from I500@ vs I50 */
255 if (*temp == 0) return (*xref == '@');
256 if (*xref != *temp) return FALSE;
257 ++xref;
258 ++temp;
259 }
260 }
261 /*==============================================
262 * is_record_temp -- Return node tree root if
263 * this is a temporary record (one not in cache)
264 *============================================*/
265 NODE
is_record_temp(RECORD rec)266 is_record_temp (RECORD rec)
267 {
268 if (!rec) return NULL;
269 return rec->rec_top;
270 }
271 /*===================================
272 * init_new_record -- put key info into record
273 * of brand new record
274 * Created: 2001/02/04, Perry Rapp
275 *=================================*/
276 void
init_new_record(RECORD rec,CNSTRING key)277 init_new_record (RECORD rec, CNSTRING key)
278 {
279 set_record_key_info(rec, key);
280 }
281 /*===================================
282 * create_record_for_keyed_node --
283 * Given a node just read from disk, wrap it in an uncached record
284 * returns addref'd record
285 *=================================*/
286 RECORD
create_record_for_keyed_node(NODE node,CNSTRING key)287 create_record_for_keyed_node (NODE node, CNSTRING key)
288 {
289 RECORD rec = alloc_new_record();
290 if (!key)
291 key = nxref(node);
292 rec->rec_top = node;
293 init_new_record(rec, key);
294 return rec;
295 }
296 /*===================================
297 * create_record_for_unkeyed_node --
298 * Given a new node tree with no key, wrap it in an uncached record
299 * returns addref'd record
300 *=================================*/
301 RECORD
create_record_for_unkeyed_node(NODE node)302 create_record_for_unkeyed_node (NODE node)
303 {
304 RECORD rec = alloc_new_record();
305 rec->rec_top = node;
306 return rec;
307 }
308 /*=================================================
309 * addref_record -- increment reference count of record
310 *===============================================*/
311 void
addref_record(RECORD rec)312 addref_record (RECORD rec)
313 {
314 if (!rec) return;
315 ASSERT(rec->vtable == &vtable_for_record);
316 ++rec->refcnt;
317 }
318 /*=================================================
319 * release_record -- decrement reference count of record
320 * and free if appropriate (ref count hits zero)
321 *===============================================*/
322 void
release_record(RECORD rec)323 release_record (RECORD rec)
324 {
325 if (!rec) return;
326 ASSERT(rec->vtable == &vtable_for_record);
327 --rec->refcnt;
328 if (!rec->refcnt) {
329 free_rec(rec);
330 }
331 }
332 /*=================================================
333 * record_destructor -- destructor for record
334 * (destructor entry in vtable)
335 *===============================================*/
336 static void
record_destructor(VTABLE * obj)337 record_destructor (VTABLE *obj)
338 {
339 RECORD rec = (RECORD)obj;
340 ASSERT((*obj) == &vtable_for_record);
341 free_rec(rec);
342 }
343 /*=================================================
344 * check_record_leaks -- Called when database closing
345 * for debugging
346 *===============================================*/
347 void
check_record_leaks(void)348 check_record_leaks (void)
349 {
350 if (f_nrecs) {
351 STRING report_leak_path = getlloptstr("ReportLeakLog", NULL);
352 FILE * fp=0;
353 if (report_leak_path)
354 fp = fopen(report_leak_path, LLAPPENDTEXT);
355 if (fp) {
356 LLDATE date;
357 get_current_lldate(&date);
358 fprintf(fp, _("Record memory leaks:"));
359 fprintf(fp, " %s", date.datestr);
360 fprintf(fp, "\n ");
361 fprintf(fp, _pl("%d item leaked", "%d items leaked", f_nrecs), f_nrecs);
362 fprintf(fp, "\n");
363 fclose(fp);
364 fp = 0;
365 }
366 }
367 }
368