1 /*
2 *
3 * Gutenprint list functions. A doubly-linked list implementation,
4 * with callbacks for freeing, sorting, and retrieving nodes by name
5 * or long name.
6 *
7 * Copyright 2002 Roger Leigh (rleigh@debian.org)
8 *
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the Free
11 * Software Foundation; either version 2 of the License, or (at your option)
12 * any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 * for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23 /*
24 * This file must include only standard C header files. The core code must
25 * compile on generic platforms that don't support glib, gimp, etc.
26 */
27
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <gutenprint/gutenprint.h>
33 #include "gutenprint-internal.h"
34 #include <gutenprint/gutenprint-intl-internal.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38
39 /** The internal representation of an stp_list_item_t list node. */
40 struct stp_list_item
41 {
42 void *data; /*!< Data */
43 struct stp_list_item *prev; /*!< Previous node */
44 struct stp_list_item *next; /*!< Next node */
45 };
46
47 /** The internal representation of an stp_list_t list. */
48 struct stp_list
49 {
50 struct stp_list_item *start; /*!< Start node */
51 struct stp_list_item *end; /*!< End node */
52 struct stp_list_item *index_cache_node; /*!< Cached node (for index) */
53 char *name_cache; /*!< Cached name */
54 struct stp_list_item *name_cache_node; /*!< Cached node (for name) */
55 char *long_name_cache; /*!< Cached long name */
56 struct stp_list_item *long_name_cache_node; /*!< Cached node (for long name) */
57 stp_node_freefunc freefunc; /*!< Callback to free node data */
58 stp_node_copyfunc copyfunc; /*!< Callback to copy node */
59 stp_node_namefunc namefunc; /*!< Callback to get node name */
60 stp_node_namefunc long_namefunc; /*!< Callback to get node long name */
61 stp_node_sortfunc sortfunc; /*!< Callback to compare (sort) nodes */
62 int index_cache; /*!< Cached node index */
63 int length; /*!< Number of nodes */
64 };
65
66 /**
67 * Cache a list node by its short name.
68 * @param list the list to use.
69 * @param name the short name.
70 * @param cache the node to cache.
71 */
72 static void
set_name_cache(stp_list_t * list,const char * name,stp_list_item_t * cache)73 set_name_cache(stp_list_t *list,
74 const char *name,
75 stp_list_item_t *cache)
76 {
77 if (list->name_cache)
78 stp_free(list->name_cache);
79 list->name_cache = NULL;
80 if (name)
81 list->name_cache = stp_strdup(name);
82 list->name_cache_node = cache;
83 }
84
85 /**
86 * Cache a list node by its long name.
87 * @param list the list to use.
88 * @param long_name the long name.
89 * @param cache the node to cache.
90 */
91 static void
set_long_name_cache(stp_list_t * list,const char * long_name,stp_list_item_t * cache)92 set_long_name_cache(stp_list_t *list,
93 const char *long_name,
94 stp_list_item_t *cache)
95 {
96 if (list->long_name_cache)
97 stp_free(list->long_name_cache);
98 list->long_name_cache = NULL;
99 if (long_name)
100 list->long_name_cache = stp_strdup(long_name);
101 list->long_name_cache_node = cache;
102 }
103
104 /**
105 * Clear cached nodes.
106 * @param list the list to use.
107 */
108 static inline void
clear_cache(stp_list_t * list)109 clear_cache(stp_list_t *list)
110 {
111 list->index_cache = 0;
112 list->index_cache_node = NULL;
113 set_name_cache(list, NULL, NULL);
114 set_long_name_cache(list, NULL, NULL);
115 }
116
117 void
stp_list_node_free_data(void * item)118 stp_list_node_free_data (void *item)
119 {
120 stp_free(item);
121 stp_deprintf(STP_DBG_LIST, "stp_list_node_free_data destructor\n");
122 }
123
124 /** Check the validity of a list. */
125 #define check_list(List) STPI_ASSERT(List != NULL, NULL)
126
127 /* List head functions.
128 * These functions operate on the list as a whole, and not the
129 * individual nodes in a list. */
130
131 /* create a new list */
132 stp_list_t *
stp_list_create(void)133 stp_list_create(void)
134 {
135 stp_list_t *list =
136 stp_malloc(sizeof(stp_list_t));
137
138 /* initialise an empty list */
139 list->index_cache = 0;
140 list->length = 0;
141 list->start = NULL;
142 list->end = NULL;
143 list->index_cache_node = NULL;
144 list->freefunc = NULL;
145 list->namefunc = NULL;
146 list->long_namefunc = NULL;
147 list->sortfunc = NULL;
148 list->copyfunc = NULL;
149 list->name_cache = NULL;
150 list->name_cache_node = NULL;
151 list->long_name_cache = NULL;
152 list->long_name_cache_node = NULL;
153
154 stp_deprintf(STP_DBG_LIST, "stp_list_head constructor\n");
155 return list;
156 }
157
158 stp_list_t *
stp_list_copy(const stp_list_t * list)159 stp_list_copy(const stp_list_t *list)
160 {
161 stp_list_t *ret;
162 stp_node_copyfunc copyfunc = stp_list_get_copyfunc(list);
163 stp_list_item_t *item = list->start;
164
165 check_list(list);
166
167 ret = stp_list_create();
168 stp_list_set_copyfunc(ret, stp_list_get_copyfunc(list));
169 /* If we use default (shallow) copy, we can't free the elements of it */
170 if (stp_list_get_copyfunc(list))
171 stp_list_set_freefunc(ret, stp_list_get_freefunc(list));
172 stp_list_set_namefunc(ret, stp_list_get_namefunc(list));
173 stp_list_set_long_namefunc(ret, stp_list_get_long_namefunc(list));
174 stp_list_set_sortfunc(ret, stp_list_get_sortfunc(list));
175 while (item)
176 {
177 void *data = item->data;
178 if (copyfunc)
179 stp_list_item_create (ret, NULL, (*copyfunc)(data));
180 else
181 stp_list_item_create(ret, NULL, data);
182 item = stp_list_item_next(item);
183 }
184 return ret;
185 }
186
187 /* free a list, freeing all child nodes first */
188 int
stp_list_destroy(stp_list_t * list)189 stp_list_destroy(stp_list_t *list)
190 {
191 stp_list_item_t *cur;
192 stp_list_item_t *next;
193
194 check_list(list);
195 clear_cache(list);
196 cur = list->start;
197 while(cur)
198 {
199 next = cur->next;
200 stp_list_item_destroy(list, cur);
201 cur = next;
202 }
203 stp_deprintf(STP_DBG_LIST, "stp_list_head destructor\n");
204 stp_free(list);
205
206 return 0;
207 }
208
209 int
stp_list_get_length(const stp_list_t * list)210 stp_list_get_length(const stp_list_t *list)
211 {
212 check_list(list);
213 return list->length;
214 }
215
216 /* find a node */
217
218 /* get the first node in the list */
219
220 stp_list_item_t *
stp_list_get_start(const stp_list_t * list)221 stp_list_get_start(const stp_list_t *list)
222 {
223 return list->start;
224 }
225
226 /* get the last node in the list */
227
228 stp_list_item_t *
stp_list_get_end(const stp_list_t * list)229 stp_list_get_end(const stp_list_t *list)
230 {
231 return list->end;
232 }
233
234 static inline stp_list_t *
deconst_list(const stp_list_t * list)235 deconst_list(const stp_list_t *list)
236 {
237 return (stp_list_t *) stpi_cast_safe(list);
238 }
239
240 /* get the node by its place in the list */
241 stp_list_item_t *
stp_list_get_item_by_index(const stp_list_t * list,int idx)242 stp_list_get_item_by_index(const stp_list_t *list, int idx)
243 {
244 stp_list_item_t *node = NULL;
245 stp_list_t *ulist = deconst_list(list);
246 int i; /* current index */
247 int d = 0; /* direction of list traversal, 0=forward */
248 int c = 0; /* use cache? */
249 check_list(list);
250
251 if (idx >= list->length)
252 return NULL;
253
254 /*
255 * Optimize the most likely cases of looking for the same, next,
256 * or previou item
257 * */
258 if (ulist->index_cache_node)
259 {
260 if (idx == ulist->index_cache)
261 return ulist->index_cache_node;
262 else if (idx == ulist->index_cache + 1)
263 {
264 ulist->index_cache = idx;
265 ulist->index_cache_node = ulist->index_cache_node->next;
266 return ulist->index_cache_node;
267 }
268 else if (idx == ulist->index_cache - 1)
269 {
270 ulist->index_cache = idx;
271 ulist->index_cache_node = ulist->index_cache_node->prev;
272 return ulist->index_cache_node;
273 }
274 }
275 /*
276 * See if using the cache is worthwhile. If the desired index is closer
277 * to the cached index than it is to the start or end, it will be faster
278 * to start from the cached element.
279 *
280 * Otherwise, decide which direction is best to start from.
281 */
282 if (list->index_cache)
283 {
284 if (idx < (list->length/2))
285 {
286 if (idx > abs(idx - list->index_cache))
287 c = 1;
288 else
289 d = 0;
290 }
291 else
292 {
293 if (list->length - 1 - idx >
294 abs (list->length - 1 - idx - list->index_cache))
295 c = 1;
296 else
297 d = 1;
298 }
299 }
300
301 /* use the cached index and node */
302 if (c)
303 {
304 if (idx > list->index_cache) /* forward */
305 d = 0;
306 else /* backward */
307 d = 1;
308 i = list->index_cache;
309 node = list->index_cache_node;
310 }
311 else /* start from one end of the list */
312 {
313 if (d)
314 {
315 i = list->length - 1;
316 node = list->end;
317 }
318 else
319 {
320 i = 0;
321 node = list->start;
322 }
323 }
324
325 while (node && i != idx)
326 {
327 if (d)
328 {
329 i--;
330 node = node->prev;
331 }
332 else
333 {
334 i++;
335 node = node->next;
336 }
337 }
338
339 /* update cache */
340 ulist->index_cache = i;
341 ulist->index_cache_node = node;
342
343 return node;
344 }
345
346 /**
347 * Find an item in a list by its name.
348 * This internal helper is not optimised to use any caching.
349 * @param list the list to use.
350 * @param name the name to find.
351 * @returns a pointer to the list item, or NULL if the name is
352 * invalid or the list is empty.
353 */
354 static stp_list_item_t *
stp_list_get_item_by_name_internal(const stp_list_t * list,const char * name)355 stp_list_get_item_by_name_internal(const stp_list_t *list, const char *name)
356 {
357 stp_list_item_t *node = list->start;
358 while (node && strcmp(name, list->namefunc(node->data)))
359 {
360 node = node->next;
361 }
362 return node;
363 }
364
365 /* get the first node with name; requires a callback function to
366 read data */
367 stp_list_item_t *
stp_list_get_item_by_name(const stp_list_t * list,const char * name)368 stp_list_get_item_by_name(const stp_list_t *list, const char *name)
369 {
370 stp_list_item_t *node = NULL;
371 stp_list_t *ulist = deconst_list(list);
372 check_list(list);
373
374 if (!list->namefunc || !name)
375 return NULL;
376
377 if (list->name_cache && list->name_cache_node)
378 {
379 const char *new_name;
380 node = list->name_cache_node;
381 /* Is this the item we've cached? */
382 if (strcmp(name, list->name_cache) == 0 &&
383 strcmp(name, list->namefunc(node->data)) == 0)
384 return node;
385
386 /* If not, check the next item in case we're searching the list */
387 node = node->next;
388 if (node)
389 {
390 new_name = list->namefunc(node->data);
391 if (strcmp(name, new_name) == 0)
392 {
393 set_name_cache(ulist, new_name, node);
394 return node;
395 }
396 }
397 /* If not, check the index cache */
398 node = list->index_cache_node;
399 if (node)
400 {
401 new_name = list->namefunc(node->data);
402 if (strcmp(name, new_name) == 0)
403 {
404 set_name_cache(ulist, new_name, node);
405 return node;
406 }
407 }
408 }
409
410 node = stp_list_get_item_by_name_internal(list, name);
411
412 if (node)
413 set_name_cache(ulist, name, node);
414
415 return node;
416 }
417
418
419 /**
420 * Find an item in a list by its long name.
421 * This internal helper is not optimised to use any caching.
422 * @param list the list to use.
423 * @param long_name the long name to find.
424 * @returns a pointer to the list item, or NULL if the long name is
425 * invalid or the list is empty.
426 */
427 static stp_list_item_t *
stp_list_get_item_by_long_name_internal(const stp_list_t * list,const char * long_name)428 stp_list_get_item_by_long_name_internal(const stp_list_t *list,
429 const char *long_name)
430 {
431 stp_list_item_t *node = list->start;
432 while (node && strcmp(long_name, list->long_namefunc(node->data)))
433 {
434 node = node->next;
435 }
436 return node;
437 }
438
439 /* get the first node with long_name; requires a callack function to
440 read data */
441 stp_list_item_t *
stp_list_get_item_by_long_name(const stp_list_t * list,const char * long_name)442 stp_list_get_item_by_long_name(const stp_list_t *list, const char *long_name)
443 {
444 stp_list_item_t *node = NULL;
445 stp_list_t *ulist = deconst_list(list);
446 check_list(list);
447
448 if (!list->long_namefunc || !long_name)
449 return NULL;
450
451 if (list->long_name_cache && list->long_name_cache_node)
452 {
453 const char *new_long_name;
454 node = list->long_name_cache_node;
455 /* Is this the item we've cached? */
456 if (strcmp(long_name, list->long_name_cache) == 0 &&
457 strcmp(long_name, list->long_namefunc(node->data)) == 0)
458 return node;
459
460 /* If not, check the next item in case we're searching the list */
461 node = node->next;
462 if (node)
463 {
464 new_long_name = list->long_namefunc(node->data);
465 if (strcmp(long_name, new_long_name) == 0)
466 {
467 set_long_name_cache(ulist, new_long_name, node);
468 return node;
469 }
470 }
471 /* If not, check the index cache */
472 node = list->index_cache_node;
473 if (node)
474 {
475 new_long_name = list->long_namefunc(node->data);
476 if (strcmp(long_name, new_long_name) == 0)
477 {
478 set_long_name_cache(ulist, new_long_name, node);
479 return node;
480 }
481 }
482 }
483
484 node = stp_list_get_item_by_long_name_internal(list, long_name);
485
486 if (node)
487 set_long_name_cache(ulist, long_name, node);
488
489 return node;
490 }
491
492
493 /* callback for freeing data */
494 void
stp_list_set_freefunc(stp_list_t * list,stp_node_freefunc freefunc)495 stp_list_set_freefunc(stp_list_t *list, stp_node_freefunc freefunc)
496 {
497 check_list(list);
498 list->freefunc = freefunc;
499 }
500
501 stp_node_freefunc
stp_list_get_freefunc(const stp_list_t * list)502 stp_list_get_freefunc(const stp_list_t *list)
503 {
504 check_list(list);
505 return list->freefunc;
506 }
507
508 /* callback for copying data */
509 void
stp_list_set_copyfunc(stp_list_t * list,stp_node_copyfunc copyfunc)510 stp_list_set_copyfunc(stp_list_t *list, stp_node_copyfunc copyfunc)
511 {
512 check_list(list);
513 list->copyfunc = copyfunc;
514 }
515
516 stp_node_copyfunc
stp_list_get_copyfunc(const stp_list_t * list)517 stp_list_get_copyfunc(const stp_list_t *list)
518 {
519 check_list(list);
520 return list->copyfunc;
521 }
522
523 /* callback for getting data name */
524 void
stp_list_set_namefunc(stp_list_t * list,stp_node_namefunc namefunc)525 stp_list_set_namefunc(stp_list_t *list, stp_node_namefunc namefunc)
526 {
527 check_list(list);
528 list->namefunc = namefunc;
529 }
530
531 stp_node_namefunc
stp_list_get_namefunc(const stp_list_t * list)532 stp_list_get_namefunc(const stp_list_t *list)
533 {
534 check_list(list);
535 return list->namefunc;
536 }
537
538 /* callback for getting data long_name */
539 void
stp_list_set_long_namefunc(stp_list_t * list,stp_node_namefunc long_namefunc)540 stp_list_set_long_namefunc(stp_list_t *list, stp_node_namefunc long_namefunc)
541 {
542 check_list(list);
543 list->long_namefunc = long_namefunc;
544 }
545
546 stp_node_namefunc
stp_list_get_long_namefunc(const stp_list_t * list)547 stp_list_get_long_namefunc(const stp_list_t *list)
548 {
549 check_list(list);
550 return list->long_namefunc;
551 }
552
553 /* callback for sorting nodes */
554 void
stp_list_set_sortfunc(stp_list_t * list,stp_node_sortfunc sortfunc)555 stp_list_set_sortfunc(stp_list_t *list, stp_node_sortfunc sortfunc)
556 {
557 check_list(list);
558 list->sortfunc = sortfunc;
559 }
560
561 stp_node_sortfunc
stp_list_get_sortfunc(const stp_list_t * list)562 stp_list_get_sortfunc(const stp_list_t *list)
563 {
564 check_list(list);
565 return list->sortfunc;
566 }
567
568
569 /* list item functions */
570
571 /* these functions operate on individual nodes in a list */
572
573 /*
574 * create a new node in list, before next (may be null e.g. if sorting
575 * next is calculated automatically, else defaults to end). Must be
576 * initialised with data (null nodes are disallowed). The
577 * stp_list_item_t type can not exist unless it is associated with an
578 * stp_list_t list head.
579 */
580 int
stp_list_item_create(stp_list_t * list,stp_list_item_t * next,const void * data)581 stp_list_item_create(stp_list_t *list,
582 stp_list_item_t *next,
583 const void *data)
584 {
585 stp_list_item_t *ln; /* list node to add */
586 stp_list_item_t *lnn; /* list node next */
587
588 check_list(list);
589
590 clear_cache(list);
591
592 ln = stp_malloc(sizeof(stp_list_item_t));
593 ln->prev = ln->next = NULL;
594
595 if (data)
596 ln->data = stpi_cast_safe(data);
597 else
598 {
599 stp_free(ln);
600 return 1;
601 }
602
603 if (list->sortfunc)
604 {
605 /* set np to the previous node (before the insertion */
606 lnn = list->end;
607 while (lnn)
608 {
609 if (list->sortfunc(lnn->data, ln->data) <= 0)
610 break;
611 lnn = lnn->prev;
612 }
613 }
614 else
615 lnn = next;
616
617 /* got lnp; now insert the new ln */
618
619 /* set next */
620 ln->next = lnn;
621
622 if (!ln->prev) /* insert at start of list */
623 {
624 if (list->start) /* list not empty */
625 ln->prev = list->end;
626 else
627 list->start = ln;
628 list->end = ln;
629 }
630
631 /* set prev (already set if at start of list) */
632
633 if (!ln->prev && ln->next) /* insert at end of list */
634 ln->prev = ln->next->prev;
635
636 if (list->start == ln->next) /* prev was old end */
637 {
638 list->start = ln;
639 }
640
641 /* set next->prev */
642 if (ln->next)
643 ln->next->prev = ln;
644
645 /* set prev->next */
646 if (ln->prev)
647 ln->prev->next = ln;
648
649 /* increment reference count */
650 list->length++;
651
652 stp_deprintf(STP_DBG_LIST, "stp_list_node constructor\n");
653 return 0;
654 }
655
656 /* remove a node from list */
657 int
stp_list_item_destroy(stp_list_t * list,stp_list_item_t * item)658 stp_list_item_destroy(stp_list_t *list, stp_list_item_t *item)
659 {
660 check_list(list);
661
662 clear_cache(list);
663 /* decrement reference count */
664 list->length--;
665
666 if (list->freefunc)
667 list->freefunc((void *) item->data);
668 if (item->prev)
669 item->prev->next = item->next;
670 else
671 list->start = item->next;
672 if (item->next)
673 item->next->prev = item->prev;
674 else
675 list->end = item->prev;
676 stp_free(item);
677
678 stp_deprintf(STP_DBG_LIST, "stp_list_node destructor\n");
679 return 0;
680 }
681
682 /* get previous node, but don't update the cache */
683 stp_list_item_t *
stp_list_item_prev(const stp_list_item_t * item)684 stp_list_item_prev(const stp_list_item_t *item)
685 {
686 return item->prev;
687 }
688
689 /* get next node, but don't update the cache */
690 stp_list_item_t *
stp_list_item_next(const stp_list_item_t * item)691 stp_list_item_next(const stp_list_item_t *item)
692 {
693 return item->next;
694 }
695
696 /* get data for node */
697 void *
stp_list_item_get_data(const stp_list_item_t * item)698 stp_list_item_get_data(const stp_list_item_t *item)
699 {
700 return item->data;
701 }
702
703 /* set data for node */
704 int
stp_list_item_set_data(stp_list_item_t * item,void * data)705 stp_list_item_set_data(stp_list_item_t *item, void *data)
706 {
707 if (data)
708 {
709 item->data = data;
710 return 0;
711 }
712 return 1; /* return error if data was NULL */
713 }
714