summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'leptonica/src/list.c')
-rw-r--r--leptonica/src/list.c814
1 files changed, 814 insertions, 0 deletions
diff --git a/leptonica/src/list.c b/leptonica/src/list.c
new file mode 100644
index 00000000..ba950923
--- /dev/null
+++ b/leptonica/src/list.c
@@ -0,0 +1,814 @@
+/*====================================================================*
+ - Copyright (C) 2001 Leptonica. All rights reserved.
+ -
+ - Redistribution and use in source and binary forms, with or without
+ - modification, are permitted provided that the following conditions
+ - are met:
+ - 1. Redistributions of source code must retain the above copyright
+ - notice, this list of conditions and the following disclaimer.
+ - 2. Redistributions in binary form must reproduce the above
+ - copyright notice, this list of conditions and the following
+ - disclaimer in the documentation and/or other materials
+ - provided with the distribution.
+ -
+ - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
+ - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*!
+ * \file list.c
+ * <pre>
+ *
+ * Inserting and removing elements
+ *
+ * void listDestroy()
+ * DLLIST *listAddToHead()
+ * l_int32 listAddToTail()
+ * l_int32 listInsertBefore()
+ * l_int32 listInsertAfter()
+ * void *listRemoveElement()
+ * void *listRemoveFromHead()
+ * void *listRemoveFromTail()
+ *
+ * Other list operations
+ *
+ * DLLIST *listFindElement()
+ * DLLIST *listFindTail()
+ * l_int32 listGetCount()
+ * l_int32 listReverse()
+ * DLLIST *listJoin()
+ *
+ * Lists are much harder to handle than arrays. There is
+ * more overhead for the programmer, both cognitive and
+ * codewise, and more likelihood that an error can be made.
+ * For that reason, lists should only be used when it is
+ * inefficient to use arrays, such as when elements are
+ * routinely inserted or deleted from inside arrays whose
+ * average size is greater than about 10.
+ *
+ * A list of data structures can be implemented in a number
+ * of ways. The two most popular are:
+ *
+ * (1) The list can be composed of a linked list of
+ * pointer cells ("cons cells"), where the data structures
+ * are hung off the cells. This is more difficult
+ * to use because you have to keep track of both
+ * your hanging data and the cell structures.
+ * It requires 3 pointers for every data structure
+ * that is put in a list. There is no problem
+ * cloning (using reference counts) for structures that
+ * are put in such a list. We implement lists by this
+ * method here.
+ *
+ * (2) The list pointers can be inserted directly into
+ * the data structures. This is easy to implement
+ * and easier to use, but it adds 2 ptrs of overhead
+ * to every data structure in which the ptrs are embedded.
+ * It also requires special care not to put the ptrs
+ * in any data that is cloned with a reference count;
+ * else your lists will break.
+ *
+ * Writing C code that uses list pointers explicitly to make
+ * and alter lists is difficult and prone to error.
+ * Consequently, a generic list utility that handles lists
+ * of arbitrary objects and doesn't force the programmer to
+ * touch the "next" and "prev" pointers, is quite useful.
+ * Such functions are provided here. However, the usual
+ * situation requires traversing a list and applying some
+ * function to one or more of the list elements. Macros
+ * for traversing the list are, in general, necessary, to
+ * achieve the goal of invisibly handling all "next" and "prev"
+ * pointers in generic lists. We provide macros for
+ * traversing a list in both forward and reverse directions.
+ *
+ * Because of the typing in C, implementation of a general
+ * list utility requires casting. If macros are used, the
+ * casting can be done implicitly; otherwise, using functions,
+ * some of the casts must be explicit. Fortunately, this
+ * can be implemented with void* so the programmer using
+ * the library will not have to make any casts! (Unless you
+ * compile with g++, in which case the rules on implicit
+ * conversion are more strict.)
+ *
+ * For example, to add an arbitrary data structure foo to the
+ * tail of a list, use
+ * listAddToTail(&head, &tail, pfoo);
+ * where head and tail are list cell ptrs and pfoo is
+ * a pointer to the foo object.
+ * And to remove an arbitrary data structure foo from a
+ * list, when you know the list cell element it is hanging from,
+ * use
+ * pfoo = listRemoveElement(&head, elem)
+ * where head and elem are list cell ptrs and pfoo is a pointer
+ * to the foo object. No casts are required for foo in
+ * either direction in ANSI C. (However, casts are
+ * required for ANSI C++).
+ *
+ * We use lists that are composed of doubly-linked
+ * cells with data structures hanging off the cells.
+ * We use doubly-linked cells to simplify insertion
+ * and deletion, and to allow operations to proceed in either
+ * direction along the list. With doubly-linked lists,
+ * it is tempting to make them circular, by setting head->prev
+ * to the tail of the list and tail->next to the head.
+ * The circular list costs nothing extra in storage, and
+ * allows operations to proceed from either end of the list
+ * with equal speed. However, the circular link adds
+ * cognitive overhead for the application programmer in
+ * general, and it greatly complicates list traversal when
+ * arbitrary list elements can be added or removed as you
+ * move through. It can be done, but in the spirit of
+ * simplicity, we avoid the temptation. The price to be paid
+ * is the extra cost to find the tail of a list -- a full
+ * traversal -- before the tail can be used. This is a
+ * cheap price to pay to avoid major headaches and buggy code.
+ *
+ * When you are only applying some function to each element
+ * in a list, you can go either forwards or backwards.
+ * To run through a list forwards, use:
+ * \code
+ * for (elem = head; elem; elem = nextelem) {
+ * nextelem = elem->next; (in case we destroy elem)
+ * <do something with elem->data>
+ * }
+ * \endcode
+ * To run through a list backwards, find the tail and use:
+ *
+ * for (elem = tail; elem; elem = prevelem) {
+ # prevelem = elem->prev; (in case we destroy elem)
+ * <do something with elem->data>
+ * }
+ *
+ * Even though these patterns are very simple, they are so common
+ * that we've provided macros for them in list.h. Using the
+ * macros, this becomes:
+ * \code
+ * L_BEGIN_LIST_FORWARD(head, elem)
+ * <do something with elem->data>
+ * L_END_LIST
+ *
+ * L_BEGIN_LIST_REVERSE(tail, elem)
+ * <do something with elem->data>
+ * L_END_LIST
+ * \endcode
+ * Note again that with macros, the application programmer does
+ * not need to refer explicitly to next and prev fields. Also,
+ * in the reverse case, note that we do not explicitly
+ * show the head of the list. However, the head of the list
+ * is always in scope, and functions can be called within the
+ * iterator that change the head.
+ *
+ * Some special cases are simpler. For example, when
+ * removing all items from the head of the list, you can use
+ * \code
+ * while (head) {
+ * obj = listRemoveFromHead(&head);
+ * <do something with obj>
+ * }
+ * \endcode
+ * Removing successive elements from the tail is equally simple:
+ * \code
+ * while (tail) {
+ * obj = listRemoveFromTail(&head, &tail);
+ * <do something with obj>
+ * }
+ * \endcode
+ * When removing an arbitrary element from a list, use
+ * \code
+ * obj = listRemoveElement(&head, elem);
+ * \endcode
+ * All the listRemove*() functions hand you the object,
+ * destroy the list cell to which it was attached, and
+ * reset the list pointers if necessary.
+ *
+ * Several other list operations, that do not involve
+ * inserting or removing objects, are also provided.
+ * The function listFindElement() locates a list pointer
+ * by matching the object hanging on it to a given
+ * object. The function listFindTail() gets a handle
+ * to the tail list ptr, allowing backwards traversals of
+ * the list. listGetCount() gives the number of elements
+ * in a list. Functions that reverse a list and concatenate
+ * two lists are also provided.
+ *
+ * These functions can be modified for efficiency in the
+ * situation where there is a large amount of creation and
+ * destruction of list cells. If millions of cells are
+ * made and destroyed, but a relatively small number are
+ * around at any time, the list cells can be stored for
+ * later re-use in a stack (see the generic stack functions
+ * in stack.c).
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*
+ * Inserting and removing elements *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief listDestroy()
+ *
+ * \param[in,out] phead head of list; will be set to null before returning
+ * \return void
+ *
+ * <pre>
+ * Notes:
+ * (1) This only destroys the cons cells. Before destroying
+ * the list, it is necessary to remove all data and set the
+ * data pointers in each cons cell to NULL.
+ * (2) listDestroy() will give a warning message for each data
+ * ptr that is not NULL.
+ * </pre>
+ */
+void
+listDestroy(DLLIST **phead)
+{
+DLLIST *elem, *next, *head;
+
+ PROCNAME("listDestroy");
+
+ if (phead == NULL) {
+ L_WARNING("ptr address is null!\n", procName);
+ return;
+ }
+
+ if ((head = *phead) == NULL)
+ return;
+
+ for (elem = head; elem; elem = next) {
+ if (elem->data)
+ L_WARNING("list data ptr is not null\n", procName);
+ next = elem->next;
+ LEPT_FREE(elem);
+ }
+ *phead = NULL;
+}
+
+
+/*!
+ * \brief listAddToHead()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] data void* ptr, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a new cell, attaches %data, and adds the
+ * cell to the head of the list.
+ * (2) When consing from NULL, be sure to initialize head to NULL
+ * before calling this function.
+ * </pre>
+ */
+l_ok
+listAddToHead(DLLIST **phead,
+ void *data)
+{
+DLLIST *cell, *head;
+
+ PROCNAME("listAddToHead");
+
+ if (!phead)
+ return ERROR_INT("&head not defined", procName, 1);
+ head = *phead;
+ if (!data)
+ return ERROR_INT("data not defined", procName, 1);
+
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ cell->data = data;
+ if (!head) { /* start the list; initialize the ptrs */
+ cell->prev = NULL;
+ cell->next = NULL;
+ } else {
+ cell->prev = NULL;
+ cell->next = head;
+ head->prev = cell;
+ }
+ *phead = cell;
+ return 0;
+}
+
+
+/*!
+ * \brief listAddToTail()
+ *
+ * \param[in,out] phead [may be updated], can be NULL
+ * \param[in,out] ptail [updated], can be NULL
+ * \param[in] data void* ptr, to be hung on tail cons cell
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This makes a new cell, attaches %data, and adds the
+ * cell to the tail of the list.
+ * (2) &head is input to allow the list to be "cons'd" up from NULL.
+ * (3) &tail is input to allow the tail to be updated
+ * for efficient sequential operation with this function.
+ * (4) We assume that if *phead and/or *ptail are not NULL,
+ * then they are valid addresses. Therefore:
+ * (a) when consing from NULL, be sure to initialize both
+ * head and tail to NULL.
+ * (b) when tail == NULL for an existing list, the tail
+ * will be found and updated.
+ * </pre>
+ */
+l_ok
+listAddToTail(DLLIST **phead,
+ DLLIST **ptail,
+ void *data)
+{
+DLLIST *cell, *head, *tail;
+
+ PROCNAME("listAddToTail");
+
+ if (!phead)
+ return ERROR_INT("&head not defined", procName, 1);
+ head = *phead;
+ if (!ptail)
+ return ERROR_INT("&tail not defined", procName, 1);
+ if (!data)
+ return ERROR_INT("data not defined", procName, 1);
+
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ cell->data = data;
+ if (!head) { /* Start the list and initialize the ptrs. *ptail
+ * should also have been initialized to NULL */
+ cell->prev = NULL;
+ cell->next = NULL;
+ *phead = cell;
+ *ptail = cell;
+ } else {
+ if ((tail = *ptail) == NULL)
+ tail = listFindTail(head);
+ cell->prev = tail;
+ cell->next = NULL;
+ tail->next = cell;
+ *ptail = cell;
+ }
+
+ return 0;
+}
+
+
+/*!
+ * \brief listInsertBefore()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] elem list element to be inserted in front of;
+ * must be NULL if head is NULL
+ * \param[in] data void* address, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This can be called on a null list, in which case both
+ * head and elem must be null.
+ * (2) If you are searching through a list, looking for a condition
+ * to add an element, you can do something like this:
+ * \code
+ * L_BEGIN_LIST_FORWARD(head, elem)
+ * <identify an elem to insert before>
+ * listInsertBefore(&head, elem, data);
+ * L_END_LIST
+ * \endcode
+ * </pre>
+ */
+l_ok
+listInsertBefore(DLLIST **phead,
+ DLLIST *elem,
+ void *data)
+{
+DLLIST *cell, *head;
+
+ PROCNAME("listInsertBefore");
+
+ if (!phead)
+ return ERROR_INT("&head not defined", procName, 1);
+ head = *phead;
+ if (!data)
+ return ERROR_INT("data not defined", procName, 1);
+ if ((!head && elem) || (head && !elem))
+ return ERROR_INT("head and elem not consistent", procName, 1);
+
+ /* New cell to insert */
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ cell->data = data;
+ if (!head) { /* start the list; initialize the ptrs */
+ cell->prev = NULL;
+ cell->next = NULL;
+ *phead = cell;
+ } else if (head == elem) { /* insert before head of list */
+ cell->prev = NULL;
+ cell->next = head;
+ head->prev = cell;
+ *phead = cell;
+ } else { /* insert before elem and after head of list */
+ cell->prev = elem->prev;
+ cell->next = elem;
+ elem->prev->next = cell;
+ elem->prev = cell;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief listInsertAfter()
+ *
+ * \param[in,out] phead [optional] input head
+ * \param[in] elem list element to be inserted after;
+ * must be NULL if head is NULL
+ * \param[in] data void* ptr, to be added
+ * \return 0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This can be called on a null list, in which case both
+ * head and elem must be null. The head is included
+ * in the call to allow "consing" up from NULL.
+ * (2) If you are searching through a list, looking for a condition
+ * to add an element, you can do something like this:
+ * \code
+ * L_BEGIN_LIST_FORWARD(head, elem)
+ * <identify an elem to insert after>
+ * listInsertAfter(&head, elem, data);
+ * L_END_LIST
+ * \endcode
+ * </pre>
+ */
+l_ok
+listInsertAfter(DLLIST **phead,
+ DLLIST *elem,
+ void *data)
+{
+DLLIST *cell, *head;
+
+ PROCNAME("listInsertAfter");
+
+ if (!phead)
+ return ERROR_INT("&head not defined", procName, 1);
+ head = *phead;
+ if (!data)
+ return ERROR_INT("data not defined", procName, 1);
+ if ((!head && elem) || (head && !elem))
+ return ERROR_INT("head and elem not consistent", procName, 1);
+
+ /* New cell to insert */
+ cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST));
+ cell->data = data;
+ if (!head) { /* start the list; initialize the ptrs */
+ cell->prev = NULL;
+ cell->next = NULL;
+ *phead = cell;
+ } else if (elem->next == NULL) { /* insert after last */
+ cell->prev = elem;
+ cell->next = NULL;
+ elem->next = cell;
+ } else { /* insert after elem and before the end */
+ cell->prev = elem;
+ cell->next = elem->next;
+ elem->next->prev = cell;
+ elem->next = cell;
+ }
+ return 0;
+}
+
+
+/*!
+ * \brief listRemoveElement()
+ *
+ * \param[in,out] phead input head; can be changed
+ * \param[in] elem list element to be removed
+ * \return data void* struct on cell
+ *
+ * <pre>
+ * Notes:
+ * (1) in ANSI C, it is not necessary to cast return to actual type; e.g.,
+ * pix = listRemoveElement(&head, elem);
+ * but in ANSI C++, it is necessary to do the cast:
+ * pix = (Pix *)listRemoveElement(&head, elem);
+ * </pre>
+ */
+void *
+listRemoveElement(DLLIST **phead,
+ DLLIST *elem)
+{
+void *data;
+DLLIST *head;
+
+ PROCNAME("listRemoveElement");
+
+ if (!phead)
+ return (void *)ERROR_PTR("&head not defined", procName, NULL);
+ head = *phead;
+ if (!head)
+ return (void *)ERROR_PTR("head not defined", procName, NULL);
+ if (!elem)
+ return (void *)ERROR_PTR("elem not defined", procName, NULL);
+
+ data = elem->data;
+
+ if (head->next == NULL) { /* only one */
+ if (elem != head)
+ return (void *)ERROR_PTR("elem must be head", procName, NULL);
+ *phead = NULL;
+ } else if (head == elem) { /* first one */
+ elem->next->prev = NULL;
+ *phead = elem->next;
+ } else if (elem->next == NULL) { /* last one */
+ elem->prev->next = NULL;
+ } else { /* neither the first nor the last one */
+ elem->next->prev = elem->prev;
+ elem->prev->next = elem->next;
+ }
+
+ LEPT_FREE(elem);
+ return data;
+}
+
+
+/*!
+ * \brief listRemoveFromHead()
+ *
+ * \param[in,out] phead head of list; updated
+ * \return data void* struct on cell, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) in ANSI C, it is not necessary to cast return to actual type; e.g.,
+ * pix = listRemoveFromHead(&head);
+ * but in ANSI C++, it is necessary to do the cast; e.g.,
+ * pix = (Pix *)listRemoveFromHead(&head);
+ * </pre>
+ */
+void *
+listRemoveFromHead(DLLIST **phead)
+{
+DLLIST *head;
+void *data;
+
+ PROCNAME("listRemoveFromHead");
+
+ if (!phead)
+ return (void *)ERROR_PTR("&head not defined", procName, NULL);
+ if ((head = *phead) == NULL)
+ return (void *)ERROR_PTR("head not defined", procName, NULL);
+
+ if (head->next == NULL) { /* only one */
+ *phead = NULL;
+ } else {
+ head->next->prev = NULL;
+ *phead = head->next;
+ }
+
+ data = head->data;
+ LEPT_FREE(head);
+ return data;
+}
+
+
+/*!
+ * \brief listRemoveFromTail()
+ *
+ * \param[in,out] phead list head must NOT be NULL; may be changed
+ * \param[in,out] ptail list tail may be NULL; always updated
+ * \return data void* struct on cell or NULL on error
+ *
+ * <pre>
+ * Notes:
+ * (1) We include &head so that it can be set to NULL if
+ * if the only element in the list is removed.
+ * (2) The function is relying on the fact that if tail is
+ * not NULL, then is is a valid address. You can use
+ * this function with tail == NULL for an existing list, in
+ * which case the tail is found and updated, and the
+ * removed element is returned.
+ * (3) In ANSI C, it is not necessary to cast return to actual type; e.g.,
+ * pix = listRemoveFromTail(&head, &tail);
+ * but in ANSI C++, it is necessary to do the cast; e.g.,
+ * pix = (Pix *)listRemoveFromTail(&head, &tail);
+ * </pre>
+ */
+void *
+listRemoveFromTail(DLLIST **phead,
+ DLLIST **ptail)
+{
+DLLIST *head, *tail;
+void *data;
+
+ PROCNAME("listRemoveFromTail");
+
+ if (!phead)
+ return (void *)ERROR_PTR("&head not defined", procName, NULL);
+ if ((head = *phead) == NULL)
+ return (void *)ERROR_PTR("head not defined", procName, NULL);
+ if (!ptail)
+ return (void *)ERROR_PTR("&tail not defined", procName, NULL);
+ if ((tail = *ptail) == NULL)
+ tail = listFindTail(head);
+
+ if (head->next == NULL) { /* only one */
+ *phead = NULL;
+ *ptail = NULL;
+ } else {
+ tail->prev->next = NULL;
+ *ptail = tail->prev;
+ }
+
+ data = tail->data;
+ LEPT_FREE(tail);
+ return data;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ * Other list operations *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief listFindElement()
+ *
+ * \param[in] head list head
+ * \param[in] data void* address, to be searched for
+ * \return cell the containing cell, or NULL if not found or on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This returns a ptr to the cell, which is still embedded in
+ * the list.
+ * (2) This handle and the attached data have not been copied or
+ * reference counted, so they must not be destroyed. This
+ * violates our basic rule that every handle returned from a
+ * function is owned by that function and must be destroyed,
+ * but if rules aren't there to be broken, why have them?
+ * </pre>
+ */
+DLLIST *
+listFindElement(DLLIST *head,
+ void *data)
+{
+DLLIST *cell;
+
+ PROCNAME("listFindElement");
+
+ if (!head)
+ return (DLLIST *)ERROR_PTR("head not defined", procName, NULL);
+ if (!data)
+ return (DLLIST *)ERROR_PTR("data not defined", procName, NULL);
+
+ for (cell = head; cell; cell = cell->next) {
+ if (cell->data == data)
+ return cell;
+ }
+
+ return NULL;
+}
+
+
+/*!
+ * \brief listFindTail()
+ *
+ * \param[in] head
+ * \return tail, or NULL on error
+ */
+DLLIST *
+listFindTail(DLLIST *head)
+{
+DLLIST *cell;
+
+ PROCNAME("listFindTail");
+
+ if (!head)
+ return (DLLIST *)ERROR_PTR("head not defined", procName, NULL);
+
+ for (cell = head; cell; cell = cell->next) {
+ if (cell->next == NULL)
+ return cell;
+ }
+
+ return (DLLIST *)ERROR_PTR("tail not found !!", procName, NULL);
+}
+
+
+/*!
+ * \brief listGetCount()
+ *
+ * \param[in] head of list
+ * \return number of elements; 0 if no list or on error
+ */
+l_int32
+listGetCount(DLLIST *head)
+{
+l_int32 count;
+DLLIST *elem;
+
+ PROCNAME("listGetCount");
+
+ if (!head)
+ return ERROR_INT("head not defined", procName, 0);
+
+ count = 0;
+ for (elem = head; elem; elem = elem->next)
+ count++;
+
+ return count;
+}
+
+
+/*!
+ * \brief listReverse()
+ *
+ * \param[in,out] phead list head; may be changed
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This reverses the list in-place.
+ * </pre>
+ */
+l_ok
+listReverse(DLLIST **phead)
+{
+void *obj; /* whatever */
+DLLIST *head, *rhead;
+
+ PROCNAME("listReverse");
+
+ if (!phead)
+ return ERROR_INT("&head not defined", procName, 1);
+ if ((head = *phead) == NULL)
+ return ERROR_INT("head not defined", procName, 1);
+
+ rhead = NULL;
+ while (head) {
+ obj = listRemoveFromHead(&head);
+ listAddToHead(&rhead, obj);
+ }
+
+ *phead = rhead;
+ return 0;
+}
+
+
+/*!
+ * \brief listJoin()
+ *
+ * \param[in,out] phead1 head of first list; may be changed
+ * \param[in,out] phead2 head of second list; to be nulled
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) The concatenated list is returned with head1 as the new head.
+ * (2) Both input ptrs must exist, though either can have the value NULL.
+ * </pre>
+ */
+l_ok
+listJoin(DLLIST **phead1,
+ DLLIST **phead2)
+{
+void *obj;
+DLLIST *head1, *head2, *tail1;
+
+ PROCNAME("listJoin");
+
+ if (!phead1)
+ return ERROR_INT("&head1 not defined", procName, 1);
+ if (!phead2)
+ return ERROR_INT("&head2 not defined", procName, 1);
+
+ /* If no list2, just return list1 unchanged */
+ if ((head2 = *phead2) == NULL)
+ return 0;
+
+ /* If no list1, just return list2 */
+ if ((head1 = *phead1) == NULL) {
+ *phead1 = head2;
+ *phead2 = NULL;
+ return 0;
+ }
+
+ /* General case for concatenation into list 1 */
+ tail1 = listFindTail(head1);
+ while (head2) {
+ obj = listRemoveFromHead(&head2);
+ listAddToTail(&head1, &tail1, obj);
+ }
+ *phead2 = NULL;
+ return 0;
+}