1 /*  =========================================================================
2     zlistx - extended generic list container
3 
4     Copyright (c) the Contributors as noted in the AUTHORS file.
5     This file is part of CZMQ, the high-level C binding for 0MQ:
6     http://czmq.zeromq.org.
7 
8     This Source Code Form is subject to the terms of the Mozilla Public
9     License, v. 2.0. If a copy of the MPL was not distributed with this
10     file, You can obtain one at http://mozilla.org/MPL/2.0/.
11     =========================================================================
12 */
13 
14 /*
15 @header
16     Provides a generic doubly-linked list container. This container provides
17     hooks for duplicator, comparator, and destructor functions. These tie
18     into CZMQ and standard C semantics, so e.g. for string items you can
19     use strdup, strcmp, and zstr_free. To store custom objects, define your
20     own duplicator and comparator, and use the standard object destructor.
21 @discuss
22     This is a reworking of the simpler zlist container. It is faster to
23     insert and delete items anywhere in the list, and to keep ordered lists.
24 @end
25 */
26 
27 #include "czmq_classes.h"
28 
29 #define NODE_TAG            0xcafe0006
30 
31 //  List node, used internally only
32 
33 typedef struct _node_t {
34     uint32_t tag;                   //  Object tag for validity checking
35     struct _node_t *next;
36     struct _node_t *prev;
37     void *item;
38 } node_t;
39 
40 
41 //  ---------------------------------------------------------------------
42 //  Structure of our class
43 
44 struct _zlistx_t {
45     node_t *head;                   //  First item in list, if any
46     node_t *cursor;                 //  Current cursors for iteration
47     size_t size;                    //  Number of items in list
48     //  Function callbacks for duplicating and destroying items, if any
49     zlistx_duplicator_fn *duplicator;
50     zlistx_destructor_fn *destructor;
51     zlistx_comparator_fn *comparator;
52 };
53 
54 
55 //  Initialize a list node and attach to the prev and next nodes, or itself
56 //  if these are specified as null. Returns new node.
57 
58 static node_t *
s_node_new(void * item)59 s_node_new (void *item)
60 {
61     node_t *self = (node_t *) zmalloc (sizeof (node_t));
62     assert (self);
63     self->tag = NODE_TAG;
64     self->prev = self;
65     self->next = self;
66     self->item = item;
67     return self;
68 }
69 
70 
71 //  Removing and inserting a node are actually the same operation:
72 //      swap (node->next, prev->next)
73 //      swap (node->prev, next->prev)
74 //  Which require only that the node be initialized to point to itself.
75 //  When inserting, node goes in between prev and next.
76 
77 static void
s_node_relink(node_t * node,node_t * prev,node_t * next)78 s_node_relink (node_t *node, node_t *prev, node_t *next)
79 {
80     node_t *temp = node->next;
81     node->next = prev->next;
82     prev->next = temp;
83     temp = node->prev;
84     node->prev = next->prev;
85     next->prev = temp;
86 }
87 
88 //  Default comparator
89 
90 static int
s_comparator(const void * item1,const void * item2)91 s_comparator (const void *item1, const void *item2)
92 {
93     if (item1 == item2)
94         return 0;
95     else
96     if (item1 < item2)
97         return -1;
98     else
99         return 1;
100 }
101 
102 
103 //  --------------------------------------------------------------------------
104 //  Create a new, empty list.
105 
106 zlistx_t *
zlistx_new(void)107 zlistx_new (void)
108 {
109     zlistx_t *self = (zlistx_t *) zmalloc (sizeof (zlistx_t));
110     assert (self);
111     self->head = s_node_new (NULL);
112     assert (self->head);
113     self->cursor = self->head;
114     self->comparator = s_comparator;
115     return self;
116 }
117 
118 
119 //  --------------------------------------------------------------------------
120 //  Destroy a list. If an item destructor was specified, all items in the
121 //  list are automatically destroyed as well.
122 
123 void
zlistx_destroy(zlistx_t ** self_p)124 zlistx_destroy (zlistx_t **self_p)
125 {
126     assert (self_p);
127     if (*self_p) {
128         zlistx_t *self = *self_p;
129         zlistx_purge (self);
130         freen (self->head);
131         freen (self);
132         *self_p = NULL;
133     }
134 }
135 
136 
137 //  --------------------------------------------------------------------------
138 //  Add an item to the head of the list. Calls the item duplicator, if any,
139 //  on the item. Resets cursor to list head. Returns an item handle on
140 //  success.
141 
142 void *
zlistx_add_start(zlistx_t * self,void * item)143 zlistx_add_start (zlistx_t *self, void *item)
144 {
145     assert (self);
146     assert (item);
147 
148     if (self->duplicator) {
149         item = (self->duplicator) (item);
150         assert (item);
151     }
152     node_t *node = s_node_new (item);
153     assert (node);
154 
155     //  Insert after head
156     s_node_relink (node, self->head, self->head->next);
157     self->cursor = self->head;
158     self->size++;
159     return node;
160 }
161 
162 
163 //  --------------------------------------------------------------------------
164 //  Add an item to the tail of the list. Calls the item duplicator, if any,
165 //  on the item. Resets cursor to list head. Returns an item handle on
166 //  success.
167 
168 void *
zlistx_add_end(zlistx_t * self,void * item)169 zlistx_add_end (zlistx_t *self, void *item)
170 {
171     assert (self);
172     assert (item);
173 
174     if (self->duplicator) {
175         item = (self->duplicator) (item);
176         assert (item);
177     }
178     node_t *node = s_node_new (item);
179     assert (node);
180 
181     //  Insert before head
182     s_node_relink (node, self->head->prev, self->head);
183     self->cursor = self->head;
184     self->size++;
185     return node;
186 }
187 
188 
189 //  --------------------------------------------------------------------------
190 //  Return the number of items in the list
191 
192 size_t
zlistx_size(zlistx_t * self)193 zlistx_size (zlistx_t *self)
194 {
195     assert (self);
196     return self->size;
197 }
198 
199 
200 //  --------------------------------------------------------------------------
201 //  Return the item at the head of list. If the list is empty, returns NULL.
202 //  Leaves cursor as-is.
203 
204 void *
zlistx_head(zlistx_t * self)205 zlistx_head (zlistx_t *self)
206 {
207     assert (self);
208     return self->head? self->head->next->item: NULL;
209 }
210 
211 
212 //  --------------------------------------------------------------------------
213 //  Return the item at the tail of list. If the list is empty, returns NULL.
214 
215 //  Leaves cursor as-is.
216 
217 void *
zlistx_tail(zlistx_t * self)218 zlistx_tail (zlistx_t *self)
219 {
220     assert (self);
221     return self->head? self->head->prev->item: NULL;
222 }
223 
224 
225 //  --------------------------------------------------------------------------
226 //  Return the item at the head of list. If the list is empty, returns NULL.
227 //  Leaves cursor pointing at the head item, or NULL if the list is empty.
228 
229 void *
zlistx_first(zlistx_t * self)230 zlistx_first (zlistx_t *self)
231 {
232     assert (self);
233     self->cursor = self->head->next;
234     return self->cursor == self->head? NULL: self->cursor->item;
235 }
236 
237 
238 //  --------------------------------------------------------------------------
239 //  Return the next item. At the end of the list (or in an empty list),
240 //  returns NULL. Use repeated zlistx_next () calls to work through the list
241 //  from zlistx_first (). First time, acts as zlistx_first().
242 
243 void *
zlistx_next(zlistx_t * self)244 zlistx_next (zlistx_t *self)
245 {
246     assert (self);
247     self->cursor = self->cursor->next;
248     return self->cursor == self->head? NULL: self->cursor->item;
249 }
250 
251 
252 //  --------------------------------------------------------------------------
253 //  Return the previous item. At the start of the list (or in an empty list),
254 //  returns NULL. Use repeated zlistx_prev () calls to work through the list
255 //  backwards from zlistx_last (). First time, acts as zlistx_last().
256 
257 void *
zlistx_prev(zlistx_t * self)258 zlistx_prev (zlistx_t *self)
259 {
260     assert (self);
261     self->cursor = self->cursor->prev;
262     return self->cursor == self->head? NULL: self->cursor->item;
263 }
264 
265 
266 //  --------------------------------------------------------------------------
267 //  Return the item at the tail of list. If the list is empty, returns NULL.
268 //  Leaves cursor pointing at the tail item, or NULL if the list is empty.
269 
270 void *
zlistx_last(zlistx_t * self)271 zlistx_last (zlistx_t *self)
272 {
273     assert (self);
274     self->cursor = self->head->prev;
275     return self->cursor == self->head? NULL: self->cursor->item;
276 }
277 
278 
279 //  --------------------------------------------------------------------------
280 //  Returns the value of the item at the cursor, or NULL if the cursor is
281 //  not pointing to an item.
282 
283 void *
zlistx_item(zlistx_t * self)284 zlistx_item (zlistx_t *self)
285 {
286     assert (self);
287     return self->cursor == self->head? NULL: self->cursor->item;
288 }
289 
290 
291 //  --------------------------------------------------------------------------
292 //  Returns the handle of the item at the cursor, or NULL if the cursor is
293 //  not pointing to an item.
294 
295 void *
zlistx_cursor(zlistx_t * self)296 zlistx_cursor (zlistx_t *self)
297 {
298     assert (self);
299     return self->cursor == self->head? NULL: self->cursor;
300 }
301 
302 //  --------------------------------------------------------------------------
303 //  Returns the item associated with the given list handle, or NULL if passed
304 //  handle is NULL. asserts that the passed in ptr points to a list node.
305 
306 void *
zlistx_handle_item(void * handle)307 zlistx_handle_item (void *handle)
308 {
309     if (!handle)
310         return NULL;
311 
312     node_t *node = (node_t *) handle;
313     assert (node->tag == NODE_TAG);
314     return node->item;
315 }
316 
317 
318 //  --------------------------------------------------------------------------
319 //  Find an item in the list, searching from the start. Uses the item
320 //  comparator, if any, else compares item values directly. Returns the
321 //  item handle found, or NULL. Sets the cursor to the found item, if any.
322 
323 void *
zlistx_find(zlistx_t * self,void * item)324 zlistx_find (zlistx_t *self, void *item)
325 {
326     assert (self);
327     assert (item);
328 
329     //  Scan list for item, this is a O(N) operation
330     node_t *node = self->head->next;
331     while (node != self->head) {
332         if (self->comparator (node->item, item) == 0) {
333             self->cursor = node;
334             return node;
335         }
336         node = node->next;
337     }
338     return NULL;
339 }
340 
341 
342 //  --------------------------------------------------------------------------
343 //  Detach an item from the list, using its handle. The item is not modified,
344 //  and the caller is responsible for destroying it if necessary. If handle is
345 //  null, detaches the first item on the list. Returns item that was detached,
346 //  or null if none was. If cursor was at item, moves cursor to previous item,
347 //  so you can detach items while iterating forwards through a list.
348 
349 void *
zlistx_detach(zlistx_t * self,void * handle)350 zlistx_detach (zlistx_t *self, void *handle)
351 {
352     assert (self);
353     node_t *node = (node_t *) handle;
354     if (!node)
355         node = self->head->next == self->head? NULL: self->head->next;
356 
357     //  Now detach node from list, without destroying item
358     if (node) {
359         //  Reposition cursor so that delete/detach works during iteration
360         if (self->cursor == node)
361             self->cursor = self->cursor->prev;
362 
363         //  Remove node from list
364         assert (node->tag == NODE_TAG);
365         s_node_relink (node, node->prev, node->next);
366         node->tag = 0xDeadBeef;
367         void *item = node->item;
368         freen (node);
369         self->size--;
370         return item;
371     }
372     else {
373         assert (self->size == 0);
374         return NULL;
375     }
376 }
377 
378 
379 //  --------------------------------------------------------------------------
380 //  Detach item at the cursor, if any, from the list. The item is not modified,
381 //  and the caller is responsible for destroying it as necessary. Returns item
382 //  that was detached, or null if none was. Moves cursor to previous item, so
383 //  you can detach items while iterating forwards through a list.
384 
385 void *
zlistx_detach_cur(zlistx_t * self)386 zlistx_detach_cur (zlistx_t *self)
387 {
388     return zlistx_detach (self, zlistx_cursor (self));
389 }
390 
391 
392 //  --------------------------------------------------------------------------
393 //  Delete an item, using its handle. Calls the item destructor if any is
394 //  set. If handle is null, deletes the first item on the list. Returns 0
395 //  if an item was deleted, -1 if not. If cursor was at item, moves cursor
396 //  to previous item, so you can delete items while iterating forwards
397 //  through a list.
398 
399 int
zlistx_delete(zlistx_t * self,void * handle)400 zlistx_delete (zlistx_t *self, void *handle)
401 {
402     assert (self);
403     void *item = zlistx_detach (self, handle);
404     if (item) {
405         if (self->destructor)
406             self->destructor (&item);
407         return 0;
408     }
409     else
410         return -1;
411 }
412 
413 
414 //  --------------------------------------------------------------------------
415 //  Move an item to the start of the list, via its handle.
416 
417 void
zlistx_move_start(zlistx_t * self,void * handle)418 zlistx_move_start (zlistx_t *self, void *handle)
419 {
420     assert (self);
421     assert (handle);
422     node_t *node = (node_t *) handle;
423     assert (node->tag == NODE_TAG);
424 
425     node_t *next = self->head->next;
426     if (node != next) {
427         //  Remove node from list, insert before next node
428         s_node_relink (node, node->prev, node->next);
429         s_node_relink (node, next->prev, next);
430     }
431 }
432 
433 
434 //  --------------------------------------------------------------------------
435 //  Move an item to the end of the list, via its handle.
436 
437 void
zlistx_move_end(zlistx_t * self,void * handle)438 zlistx_move_end (zlistx_t *self, void *handle)
439 {
440     assert (self);
441     assert (handle);
442     node_t *node = (node_t *) handle;
443     assert (node->tag == NODE_TAG);
444 
445     node_t *prev = self->head->prev;
446     if (node != prev) {
447         //  Remove node from list, insert after prev node
448         s_node_relink (node, node->prev, node->next);
449         s_node_relink (node, prev, prev->next);
450     }
451 }
452 
453 
454 //  --------------------------------------------------------------------------
455 //  Remove all items from the list, and destroy them if the item destructor
456 //  is set.
457 
458 void
zlistx_purge(zlistx_t * self)459 zlistx_purge (zlistx_t *self)
460 {
461     assert (self);
462     while (zlistx_size (self) > 0)
463         zlistx_delete (self, NULL);
464 }
465 
466 
467 //  --------------------------------------------------------------------------
468 //  Sort the list. If an item comparator was set, calls that to compare
469 //  items, otherwise compares on item value. The sort is not stable, so may
470 //  reorder equal items.
471 
472 void
zlistx_sort(zlistx_t * self)473 zlistx_sort (zlistx_t *self)
474 {
475     //  Uses a comb sort, which is simple and reasonably fast
476     //  See http://en.wikipedia.org/wiki/Comb_sort
477     assert (self);
478     size_t gap = self->size;
479     bool swapped = false;
480     while (gap > 1 || swapped) {
481         gap = (size_t) ((double) gap / 1.3);
482         node_t *base = self->head->next;
483         node_t *test = self->head->next;
484         size_t jump = gap;
485         while (jump--)
486             test = test->next;
487 
488         swapped = false;
489         while (base != self->head && test != self->head) {
490             if (self->comparator (base->item, test->item) > 0) {
491                 //  We don't actually swap nodes, just the items in the nodes.
492                 //  This is ridiculously simple and confuses the heck out of
493                 //  me every time I re-read the code, as I expect to see the
494                 //  nodes being swapped. Hence this comment. -- PH 2014/09/06
495                 void *item = base->item;
496                 base->item = test->item;
497                 test->item = item;
498                 swapped = true;
499             }
500             base = base->next;
501             test = test->next;
502         }
503     }
504 }
505 
506 
507 //  --------------------------------------------------------------------------
508 //  Create a new node and insert it into a sorted list. Calls the item
509 //  duplicator, if any, on the item. If low_value is true, starts searching
510 //  from the start of the list, otherwise searches from the end. Use the item
511 //  comparator, if any, to find where to place the new node. Returns a handle
512 //  to the new node, or NULL if memory was exhausted. Resets the cursor to the
513 //  list head.
514 
515 void *
zlistx_insert(zlistx_t * self,void * item,bool low_value)516 zlistx_insert (zlistx_t *self, void *item, bool low_value)
517 {
518     assert (self);
519     if (self->duplicator) {
520         item = (self->duplicator) (item);
521         assert (item);
522     }
523     node_t *node = s_node_new (item);
524     assert (node);
525     zlistx_reorder (self, node, low_value);
526     self->cursor = self->head;
527     self->size++;
528     return node;
529 }
530 
531 
532 //  --------------------------------------------------------------------------
533 //  Move an item, specified by handle, into position in a sorted list. Uses
534 //  the item comparator, if any, to determine the new location. If low_value
535 //  is true, starts searching from the start of the list, otherwise searches
536 //  from the end.
537 
538 void
zlistx_reorder(zlistx_t * self,void * handle,bool low_value)539 zlistx_reorder (zlistx_t *self, void *handle, bool low_value)
540 {
541     assert (self);
542     assert (handle);
543     node_t *node = (node_t *) handle;
544     assert (node->tag == NODE_TAG);
545 
546     //  Remove node from list, if it's attached
547     s_node_relink (node, node->prev, node->next);
548 
549     if (low_value) {
550         node_t *next = self->head->next;
551         while (next != self->head) {
552             if (self->comparator (node->item, next->item) <= 0)
553                 break;
554             next = next->next;
555         }
556         //  Relink node before next node
557         s_node_relink (node, next->prev, next);
558     }
559     else {
560         node_t *prev = self->head->prev;
561         while (prev != self->head) {
562             if (self->comparator (prev->item, node->item) <= 0)
563                 break;
564             prev = prev->prev;
565         }
566         //  Relink node after prev node
567         s_node_relink (node, prev, prev->next);
568     }
569 }
570 
571 
572 //  --------------------------------------------------------------------------
573 //  Make a copy of the list; items are duplicated if you set a duplicator
574 //  for the list, otherwise not. Copying a null reference returns a null
575 //  reference.
576 
577 zlistx_t *
zlistx_dup(zlistx_t * self)578 zlistx_dup (zlistx_t *self)
579 {
580     if (!self)
581         return NULL;
582 
583     zlistx_t *copy = zlistx_new ();
584     if (copy) {
585         //  Copy item handlers
586         copy->destructor = self->destructor;
587         copy->duplicator = self->duplicator;
588         copy->comparator = self->comparator;
589 
590         //  Copy nodes
591         node_t *node;
592         for (node = self->head->next; node != self->head; node = node->next)
593             zlistx_add_end (copy, node->item);
594     }
595     return copy;
596 }
597 
598 
599 //  --------------------------------------------------------------------------
600 //  Set a user-defined deallocator for list items; by default items are not
601 //  freed when the list is destroyed.
602 
603 void
zlistx_set_destructor(zlistx_t * self,zlistx_destructor_fn destructor)604 zlistx_set_destructor (zlistx_t *self, zlistx_destructor_fn destructor)
605 {
606     assert (self);
607     self->destructor = destructor;
608 }
609 
610 
611 //  --------------------------------------------------------------------------
612 //  Set a user-defined duplicator for list items; by default items are not
613 //  copied when the list is duplicated.
614 
615 void
zlistx_set_duplicator(zlistx_t * self,zlistx_duplicator_fn duplicator)616 zlistx_set_duplicator (zlistx_t *self, zlistx_duplicator_fn duplicator)
617 {
618     assert (self);
619     self->duplicator = duplicator;
620 }
621 
622 
623 //  --------------------------------------------------------------------------
624 //  Set a user-defined comparator for zlistx_find and zlistx_sort; the method
625 //  must return -1, 0, or 1 depending on whether item1 is less than, equal to,
626 //  or greater than, item2.
627 
628 void
zlistx_set_comparator(zlistx_t * self,zlistx_comparator_fn comparator)629 zlistx_set_comparator (zlistx_t *self, zlistx_comparator_fn comparator)
630 {
631     assert (self);
632     self->comparator = comparator;
633 }
634 
635 //  --------------------------------------------------------------------------
636 //  Serialize list to a binary frame that can be sent in a message.
637 //  The packed format is compatible with the 'strings' type implemented by zproto:
638 //
639 //     ; A list of strings
640 //     list            = list-count *longstr
641 //     list-count      = number-4
642 //
643 //     ; Strings are always length + text contents
644 //     longstr         = number-4 *VCHAR
645 //
646 //     ; Numbers are unsigned integers in network byte order
647 //     number-4        = 4OCTET
648 //  Caller owns return value and must destroy it when done.
649 zframe_t *
zlistx_pack(zlistx_t * self)650 zlistx_pack (zlistx_t *self) {
651     assert (self);
652 
653     //  First, calculate the packed data size
654     size_t frame_size = 4;      // List size, number-4
655     char* item = (char *) zlistx_first (self);
656 
657     while (item) {
658         frame_size += 4 + strlen (item);
659         item = (char *) zlistx_next (self);
660     }
661 
662     //  Now serialize items into the frame
663     zframe_t *frame = zframe_new (NULL, frame_size);
664     if (!frame)
665         return NULL;
666 
667     byte *needle = zframe_data (frame);
668     *(uint32_t *) needle = htonl ((u_long) self->size);
669     needle += 4;
670 
671     item = (char *) zlistx_first (self);
672     while (item) {
673         size_t length = strlen (item);
674         uint32_t serialize = htonl ((u_long) length);
675         memcpy (needle, &serialize, 4);
676         needle += 4;
677         memcpy (needle, item, length);
678         needle += length;
679 
680         item = (char *) zlistx_next (self);
681     }
682 
683     return frame;
684 }
685 
686 
687 //  --------------------------------------------------------------------------
688 //  Unpack binary frame into a new list. Packed data must follow format
689 //  defined by zlistx_pack. List is set to autofree. An empty frame
690 //  unpacks to an empty list.
691 zlistx_t *
zlistx_unpack(zframe_t * frame)692 zlistx_unpack (zframe_t *frame) {
693     zlistx_t *self = zlistx_new ();
694     if (!self)
695         return NULL;
696 
697     //  List will free values in destructor
698     zlistx_set_destructor (self, (zlistx_destructor_fn *) zstr_free);
699 
700     assert (frame);
701     if (zframe_size (frame) < 4)
702         return self;
703 
704     byte *needle = zframe_data (frame);
705     byte *ceiling = needle + zframe_size (frame);
706     size_t nbr_items = ntohl (*(uint32_t *) needle);
707     needle +=4;
708     while (nbr_items && needle < ceiling) {
709         if (needle + 4 <= ceiling) {
710             size_t length = ntohl (*(uint32_t *)needle);
711             needle += 4;
712             // Be wary of malformed frames
713             if (needle + length <= ceiling) {
714                 char * item = (char *) zmalloc (length + 1);
715                 assert (item);
716                 memcpy (item, needle, length);
717                 item[length] = 0;
718                 needle += length;
719 
720                 if (!zlistx_add_end (self, item)) {
721                     zlistx_destroy (&self);
722                     break;
723                 }
724             } else {
725 
726                 zlistx_destroy (&self);
727                 break;
728             }
729         } else {
730             zlistx_destroy (&self);
731             break;
732         }
733     }
734 
735     if (self)
736     	zlistx_set_duplicator (self, (zlistx_duplicator_fn *) strdup);
737 
738     return self;
739 }
740 
741 
742 //  --------------------------------------------------------------------------
743 //  Runs selftest of class
744 
745 void
zlistx_test(bool verbose)746 zlistx_test (bool verbose)
747 {
748     printf (" * zlistx: ");
749 
750     //  @selftest
751     zlistx_t *list = zlistx_new ();
752     assert (list);
753     assert (zlistx_size (list) == 0);
754 
755     //  Test operations on an empty list
756     assert (zlistx_head (list) == NULL);
757     assert (zlistx_first (list) == NULL);
758     assert (zlistx_last (list) == NULL);
759     assert (zlistx_next (list) == NULL);
760     assert (zlistx_prev (list) == NULL);
761     assert (zlistx_find (list, "hello") == NULL);
762     assert (zlistx_delete (list, NULL) == -1);
763     assert (zlistx_detach (list, NULL) == NULL);
764     assert (zlistx_delete (list, NULL) == -1);
765     assert (zlistx_detach (list, NULL) == NULL);
766     zlistx_purge (list);
767     zlistx_sort (list);
768 
769     //  Use item handlers
770     zlistx_set_destructor (list, (zlistx_destructor_fn *) zstr_free);
771     zlistx_set_duplicator (list, (zlistx_duplicator_fn *) strdup);
772     zlistx_set_comparator (list, (zlistx_comparator_fn *) strcmp);
773 
774     //  Try simple insert/sort/delete/next
775     assert (zlistx_next (list) == NULL);
776     zlistx_add_end (list, "world");
777     assert (streq ((char *) zlistx_next (list), "world"));
778     assert (streq ((char *) zlistx_head (list), "world"));
779     zlistx_add_end (list, "hello");
780     assert (streq ((char *) zlistx_prev (list), "hello"));
781     zlistx_sort (list);
782     assert (zlistx_size (list) == 2);
783     void *handle = zlistx_find (list, "hello");
784     char *item1 = (char *) zlistx_item (list);
785     char *item2 = (char *) zlistx_handle_item (handle);
786     assert (item1 == item2);
787     assert (streq (item1, "hello"));
788     zlistx_delete (list, handle);
789     assert (zlistx_size (list) == 1);
790     char *string = (char *) zlistx_detach (list, NULL);
791     assert (streq (string, "world"));
792     freen (string);
793     assert (zlistx_size (list) == 0);
794 
795     //  Check next/back work
796     //  Now populate the list with items
797     zlistx_add_start (list, "five");
798     zlistx_add_end   (list, "six");
799     zlistx_add_start (list, "four");
800     zlistx_add_end   (list, "seven");
801     zlistx_add_start (list, "three");
802     zlistx_add_end   (list, "eight");
803     zlistx_add_start (list, "two");
804     zlistx_add_end   (list, "nine");
805     zlistx_add_start (list, "one");
806     zlistx_add_end   (list, "ten");
807 
808     //  Test our navigation skills
809     assert (zlistx_size (list) == 10);
810     assert (streq ((char *) zlistx_last (list), "ten"));
811     assert (streq ((char *) zlistx_prev (list), "nine"));
812     assert (streq ((char *) zlistx_prev (list), "eight"));
813     assert (streq ((char *) zlistx_prev (list), "seven"));
814     assert (streq ((char *) zlistx_prev (list), "six"));
815     assert (streq ((char *) zlistx_prev (list), "five"));
816     assert (streq ((char *) zlistx_first (list), "one"));
817     assert (streq ((char *) zlistx_next (list), "two"));
818     assert (streq ((char *) zlistx_next (list), "three"));
819     assert (streq ((char *) zlistx_next (list), "four"));
820 
821     //  Sort by alphabetical order
822     zlistx_sort (list);
823     assert (streq ((char *) zlistx_first (list), "eight"));
824     assert (streq ((char *) zlistx_last (list), "two"));
825 
826     //  Moving items around
827     handle = zlistx_find (list, "six");
828     zlistx_move_start (list, handle);
829     assert (streq ((char *) zlistx_first (list), "six"));
830     zlistx_move_end (list, handle);
831     assert (streq ((char *) zlistx_last (list), "six"));
832     zlistx_sort (list);
833     assert (streq ((char *) zlistx_last (list), "two"));
834 
835     //  Copying a list
836     zlistx_t *copy = zlistx_dup (list);
837     assert (copy);
838     assert (zlistx_size (copy) == 10);
839     assert (streq ((char *) zlistx_first (copy), "eight"));
840     assert (streq ((char *) zlistx_last (copy), "two"));
841     zlistx_destroy (&copy);
842 
843     //  Delete items while iterating
844     string = (char *) zlistx_first (list);
845     assert (streq (string, "eight"));
846     string = (char *) zlistx_next (list);
847     assert (streq (string, "five"));
848     zlistx_delete (list, zlistx_cursor (list));
849     string = (char *) zlistx_next (list);
850     assert (streq (string, "four"));
851 
852     //  Test pack/unpack methods
853     zframe_t *frame = zlistx_pack (list);
854     copy = zlistx_unpack (frame);
855     assert (copy);
856     zframe_destroy (&frame);
857     assert (zlistx_size (copy) == zlistx_size (list));
858 
859     char *item_orig = (char *) zlistx_first (list);
860     char *item_copy = (char *) zlistx_first (copy);
861     while (item_orig) {
862         assert (strcmp(item_orig, item_copy) == 0);
863         item_orig = (char *) zlistx_next (list);
864         item_copy = (char *) zlistx_next (copy);
865     }
866 
867     zlistx_destroy (&copy);
868 
869     zlistx_purge (list);
870     zlistx_destroy (&list);
871 
872 #if defined (__WINDOWS__)
873     zsys_shutdown();
874 #endif
875     //  @end
876 
877     printf ("OK\n");
878 }
879