/** * @file linked_lists.h * @brief LLLinkedList class header amd implementation file. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #ifndef LL_LINKED_LISTS_H #define LL_LINKED_LISTS_H /** * Provides a standard doubly linked list for fun and profit * Utilizes a neat trick off of Flipcode where the back pointer is a * pointer to a pointer, allowing easier transfer of nodes between lists, &c * And a template class, of course */ #include "llerror.h" template <class DATA_TYPE> class LLLinkedList { public: friend class LLLinkNode; // External interface // basic constructor LLLinkedList() : mHead(NULL), mCurrentp(NULL), mInsertBefore(NULL) { mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; mCount = 0; } // basic constructor LLLinkedList(BOOL (*insert_before)(DATA_TYPE *data_new, DATA_TYPE *data_tested)) : mHead(NULL), mCurrentp(NULL), mInsertBefore(insert_before) { mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; mCount = 0; } // destructor destroys list and nodes, but not data in nodes ~LLLinkedList() { removeAllNodes(); } // set mInsertBefore void setInsertBefore(BOOL (*insert_before)(DATA_TYPE *data_new, DATA_TYPE *data_tested)) { mInsertBefore = insert_before; } // // WARNING!!!!!!! // addData and addDataSorted are NOT O(1) operations, but O(n) because they check // for existence of the data in the linked list first. Why, I don't know - djs // If you don't care about dupes, use addDataNoCheck // // put data into a node and stick it at the front of the list inline BOOL addData(DATA_TYPE *data); // put data into a node and sort into list by mInsertBefore() // calls normal add if mInsertBefore isn't set inline BOOL addDataSorted(DATA_TYPE *data); inline BOOL addDataNoCheck(DATA_TYPE *data); // bubbleSortList // does an improved bubble sort of the list . . . works best with almost sorted data // does nothing if mInsertBefore isn't set // Nota Bene: Swaps are accomplished by swapping data pointers inline void bubbleSortList(); // put data into a node and stick it at the end of the list inline BOOL addDataAtEnd(DATA_TYPE *data); // returns number of items in the list inline S32 getLength() const; inline BOOL isEmpty(); // search the list starting at mHead.mNextp and remove the link with mDatap == data // leave mCurrentp and mCurrentOperatingp on the next entry // return TRUE if found, FALSE if not found inline BOOL removeData(DATA_TYPE *data); // search the list starting at mHead.mNextp and delete the link with mDatap == data // leave mCurrentp and mCurrentOperatingp on the next entry // return TRUE if found, FALSE if not found inline BOOL deleteData(DATA_TYPE *data); // remove all nodes from the list and delete the associated data inline void deleteAllData(); // remove all nodes from the list but do not delete data inline void removeAllNodes(); // check to see if data is in list // if TRUE then mCurrentp and mCurrentOperatingp point to data inline BOOL checkData(DATA_TYPE *data); // place mCurrentp on first node inline void resetList(); // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp inline DATA_TYPE *getCurrentData(); // same as getCurrentData() but a more intuitive name for the operation inline DATA_TYPE *getNextData(); // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp inline DATA_TYPE *getFirstData(); // reset the list and return the data at position n, set mCurentOperatingp to that node and bump mCurrentp // Note: n is zero-based inline DATA_TYPE *getNthData( U32 n); // reset the list and return the last data in it, set mCurentOperatingp to that node and bump mCurrentp inline DATA_TYPE *getLastData(); // remove the Node at mCurentOperatingp // leave mCurrentp and mCurentOperatingp on the next entry inline void removeCurrentData(); // remove the Node at mCurentOperatingp and add it to newlist // leave mCurrentp and mCurentOperatingp on the next entry void moveCurrentData(LLLinkedList *newlist, BOOL b_sort); BOOL moveData(DATA_TYPE *data, LLLinkedList *newlist, BOOL b_sort); // delete the Node at mCurentOperatingp // leave mCurrentp anf mCurentOperatingp on the next entry void deleteCurrentData(); private: // node that actually contains the data class LLLinkNode { public: // assign the mDatap pointer LLLinkNode(DATA_TYPE *data) : mDatap(data), mNextp(NULL), mPrevpp(NULL) { } // destructor does not, by default, destroy associated data // however, the mDatap must be NULL to ensure that we aren't causing memory leaks ~LLLinkNode() { if (mDatap) { llerror("Attempting to call LLLinkNode destructor with a non-null mDatap!", 1); } } // delete associated data and NULL out pointer void deleteData() { delete mDatap; mDatap = NULL; } // NULL out pointer void removeData() { mDatap = NULL; } DATA_TYPE *mDatap; LLLinkNode *mNextp; LLLinkNode **mPrevpp; }; // add a node at the front of the list void addData(LLLinkNode *node) { // don't allow NULL to be passed to addData if (!node) { llerror("NULL pointer passed to LLLinkedList::addData", 0); } // add the node to the front of the list node->mPrevpp = &mHead.mNextp; node->mNextp = mHead.mNextp; // if there's something in the list, fix its back pointer if (node->mNextp) { node->mNextp->mPrevpp = &node->mNextp; } mHead.mNextp = node; } LLLinkNode mHead; // fake head node. . . makes pointer operations faster and easier LLLinkNode *mCurrentp; // mCurrentp is the Node that getCurrentData returns LLLinkNode *mCurrentOperatingp; // this is the node that the various mumbleCurrentData functions act on BOOL (*mInsertBefore)(DATA_TYPE *data_new, DATA_TYPE *data_tested); // user function set to allow sorted lists U32 mCount; }; template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::addData(DATA_TYPE *data) { // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::addData", 0); } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; if ( checkData(data)) { mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return FALSE; } // make the new node LLLinkNode *temp = new LLLinkNode(data); // add the node to the front of the list temp->mPrevpp = &mHead.mNextp; temp->mNextp = mHead.mNextp; // if there's something in the list, fix its back pointer if (temp->mNextp) { temp->mNextp->mPrevpp = &temp->mNextp; } mHead.mNextp = temp; mCurrentp = tcurr; mCurrentOperatingp = tcurrop; mCount++; return TRUE; } template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::addDataNoCheck(DATA_TYPE *data) { // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::addData", 0); } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; // make the new node LLLinkNode *temp = new LLLinkNode(data); // add the node to the front of the list temp->mPrevpp = &mHead.mNextp; temp->mNextp = mHead.mNextp; // if there's something in the list, fix its back pointer if (temp->mNextp) { temp->mNextp->mPrevpp = &temp->mNextp; } mHead.mNextp = temp; mCurrentp = tcurr; mCurrentOperatingp = tcurrop; mCount++; return TRUE; } template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::addDataSorted(DATA_TYPE *data) { LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::addDataSorted", 0); } if (checkData(data)) { // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return FALSE; } // mInsertBefore not set? if (!mInsertBefore) { addData(data); // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return FALSE; } // empty list? if (!mHead.mNextp) { addData(data); // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return TRUE; } // make the new node LLLinkNode *temp = new LLLinkNode(data); // walk the list until mInsertBefore returns true mCurrentp = mHead.mNextp; while (mCurrentp->mNextp) { if (mInsertBefore(data, mCurrentp->mDatap)) { // insert before the current one temp->mPrevpp = mCurrentp->mPrevpp; temp->mNextp = mCurrentp; *(temp->mPrevpp) = temp; mCurrentp->mPrevpp = &temp->mNextp; // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; mCount++; return TRUE; } else { mCurrentp = mCurrentp->mNextp; } } // on the last element, add before? if (mInsertBefore(data, mCurrentp->mDatap)) { // insert before the current one temp->mPrevpp = mCurrentp->mPrevpp; temp->mNextp = mCurrentp; *(temp->mPrevpp) = temp; mCurrentp->mPrevpp = &temp->mNextp; // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; } else // insert after { temp->mPrevpp = &mCurrentp->mNextp; temp->mNextp = NULL; mCurrentp->mNextp = temp; // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; } mCount++; return TRUE; } template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::bubbleSortList() { // mInsertBefore not set if (!mInsertBefore) { return; } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; BOOL b_swapped = FALSE; DATA_TYPE *temp; // Nota Bene: This will break if more than 0x7FFFFFFF members in list! S32 length = 0x7FFFFFFF; S32 count = 0; do { b_swapped = FALSE; mCurrentp = mHead.mNextp; count = 0; while ( (count + 1 < length) &&(mCurrentp)) { if (mCurrentp->mNextp) { if (!mInsertBefore(mCurrentp->mDatap, mCurrentp->mNextp->mDatap)) { // swap data pointers! temp = mCurrentp->mDatap; mCurrentp->mDatap = mCurrentp->mNextp->mDatap; mCurrentp->mNextp->mDatap = temp; b_swapped = TRUE; } } else { break; } count++; mCurrentp = mCurrentp->mNextp; } length = count; } while (b_swapped); // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; } template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::addDataAtEnd(DATA_TYPE *data) { LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::addData", 0); } if (checkData(data)) { mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return FALSE; } // make the new node LLLinkNode *temp = new LLLinkNode(data); // add the node to the end of the list // if empty, add to the front and be done with it if (!mHead.mNextp) { temp->mPrevpp = &mHead.mNextp; temp->mNextp = NULL; mHead.mNextp = temp; } else { // otherwise, walk to the end of the list mCurrentp = mHead.mNextp; while (mCurrentp->mNextp) { mCurrentp = mCurrentp->mNextp; } temp->mPrevpp = &mCurrentp->mNextp; temp->mNextp = NULL; mCurrentp->mNextp = temp; } // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; mCount++; return TRUE; } // returns number of items in the list template <class DATA_TYPE> S32 LLLinkedList<DATA_TYPE>::getLength() const { // S32 length = 0; // for (LLLinkNode* temp = mHead.mNextp; temp != NULL; temp = temp->mNextp) // { // length++; // } return mCount; } template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::isEmpty() { return (mCount == 0); } // search the list starting at mHead.mNextp and remove the link with mDatap == data // leave mCurrentp and mCurrentOperatingp on the next entry // return TRUE if found, FALSE if not found template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::removeData(DATA_TYPE *data) { BOOL b_found = FALSE; // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::removeData", 0); } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; while (mCurrentOperatingp) { if (mCurrentOperatingp->mDatap == data) { b_found = TRUE; // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // remove the LLLinkNode // if we were on the one we want to delete, bump the cached copies if (mCurrentOperatingp == tcurrop) { tcurrop = tcurr = mCurrentOperatingp->mNextp; } else if (mCurrentOperatingp == tcurr) { tcurrop = tcurr = mCurrentOperatingp->mNextp; } mCurrentp = mCurrentOperatingp->mNextp; mCurrentOperatingp->removeData(); delete mCurrentOperatingp; mCurrentOperatingp = mCurrentp; mCount--; break; } mCurrentOperatingp = mCurrentOperatingp->mNextp; } // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return b_found; } // search the list starting at mHead.mNextp and delete the link with mDatap == data // leave mCurrentp and mCurrentOperatingp on the next entry // return TRUE if found, FALSE if not found template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::deleteData(DATA_TYPE *data) { BOOL b_found = FALSE; // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::removeData", 0); } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; while (mCurrentOperatingp) { if (mCurrentOperatingp->mDatap == data) { b_found = TRUE; // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // delete the LLLinkNode // if we were on the one we want to delete, bump the cached copies if (mCurrentOperatingp == tcurrop) { tcurrop = tcurr = mCurrentOperatingp->mNextp; } // and delete the associated data llassert(mCurrentOperatingp); mCurrentp = mCurrentOperatingp->mNextp; mCurrentOperatingp->deleteData(); delete mCurrentOperatingp; mCurrentOperatingp = mCurrentp; mCount--; break; } mCurrentOperatingp = mCurrentOperatingp->mNextp; } // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return b_found; } // remove all nodes from the list and delete the associated data template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::deleteAllData() { LLLinkNode *temp; // reset mCurrentp mCurrentp = mHead.mNextp; while (mCurrentp) { temp = mCurrentp->mNextp; mCurrentp->deleteData(); delete mCurrentp; mCurrentp = temp; } // reset mHead and mCurrentp mHead.mNextp = NULL; mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; mCount = 0; } // remove all nodes from the list but do not delete data template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::removeAllNodes() { LLLinkNode *temp; // reset mCurrentp mCurrentp = mHead.mNextp; while (mCurrentp) { temp = mCurrentp->mNextp; mCurrentp->removeData(); delete mCurrentp; mCurrentp = temp; } // reset mHead and mCurrentp mHead.mNextp = NULL; mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; mCount = 0; } // check to see if data is in list // if TRUE then mCurrentp and mCurrentOperatingp point to data template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::checkData(DATA_TYPE *data) { // reset mCurrentp mCurrentp = mHead.mNextp; while (mCurrentp) { if (mCurrentp->mDatap == data) { mCurrentOperatingp = mCurrentp; return TRUE; } mCurrentp = mCurrentp->mNextp; } mCurrentOperatingp = mCurrentp; return FALSE; } // place mCurrentp on first node template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::resetList() { mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; } // return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp template <class DATA_TYPE> DATA_TYPE *LLLinkedList<DATA_TYPE>::getCurrentData() { if (mCurrentp) { mCurrentOperatingp = mCurrentp; mCurrentp = mCurrentp->mNextp; return mCurrentOperatingp->mDatap; } else { return NULL; } } // same as getCurrentData() but a more intuitive name for the operation template <class DATA_TYPE> DATA_TYPE *LLLinkedList<DATA_TYPE>::getNextData() { if (mCurrentp) { mCurrentOperatingp = mCurrentp; mCurrentp = mCurrentp->mNextp; return mCurrentOperatingp->mDatap; } else { return NULL; } } // reset the list and return the data currently pointed to, set mCurentOperatingp to that node and bump mCurrentp template <class DATA_TYPE> DATA_TYPE *LLLinkedList<DATA_TYPE>::getFirstData() { mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; if (mCurrentp) { mCurrentOperatingp = mCurrentp; mCurrentp = mCurrentp->mNextp; return mCurrentOperatingp->mDatap; } else { return NULL; } } // Note: n is zero-based template <class DATA_TYPE> DATA_TYPE *LLLinkedList<DATA_TYPE>::getNthData( U32 n ) { mCurrentOperatingp = mHead.mNextp; // if empty, return NULL if (!mCurrentOperatingp) { return NULL; } for( U32 i = 0; i < n; i++ ) { mCurrentOperatingp = mCurrentOperatingp->mNextp; if( !mCurrentOperatingp ) { return NULL; } } mCurrentp = mCurrentOperatingp->mNextp; return mCurrentOperatingp->mDatap; } // reset the list and return the last data in it, set mCurentOperatingp to that node and bump mCurrentp template <class DATA_TYPE> DATA_TYPE *LLLinkedList<DATA_TYPE>::getLastData() { mCurrentOperatingp = mHead.mNextp; // if empty, return NULL if (!mCurrentOperatingp) return NULL; // walk until we're pointing at the last entry while (mCurrentOperatingp->mNextp) { mCurrentOperatingp = mCurrentOperatingp->mNextp; } mCurrentp = mCurrentOperatingp->mNextp; return mCurrentOperatingp->mDatap; } // remove the Node at mCurentOperatingp // leave mCurrentp and mCurentOperatingp on the next entry // return TRUE if found, FALSE if not found template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::removeCurrentData() { if (mCurrentOperatingp) { // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // remove the LLLinkNode mCurrentp = mCurrentOperatingp->mNextp; mCurrentOperatingp->removeData(); delete mCurrentOperatingp; mCount--; mCurrentOperatingp = mCurrentp; } } // remove the Node at mCurentOperatingp and add it to newlist // leave mCurrentp and mCurentOperatingp on the next entry // return TRUE if found, FALSE if not found template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::moveCurrentData(LLLinkedList *newlist, BOOL b_sort) { if (mCurrentOperatingp) { // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // remove the LLLinkNode mCurrentp = mCurrentOperatingp->mNextp; // move the node to the new list newlist->addData(mCurrentOperatingp); if (b_sort) bubbleSortList(); mCurrentOperatingp = mCurrentp; } } template <class DATA_TYPE> BOOL LLLinkedList<DATA_TYPE>::moveData(DATA_TYPE *data, LLLinkedList *newlist, BOOL b_sort) { BOOL b_found = FALSE; // don't allow NULL to be passed to addData if (!data) { llerror("NULL pointer passed to LLLinkedList::removeData", 0); } LLLinkNode *tcurr = mCurrentp; LLLinkNode *tcurrop = mCurrentOperatingp; mCurrentp = mHead.mNextp; mCurrentOperatingp = mHead.mNextp; while (mCurrentOperatingp) { if (mCurrentOperatingp->mDatap == data) { b_found = TRUE; // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // if we were on the one we want to delete, bump the cached copies if ( (mCurrentOperatingp == tcurrop) ||(mCurrentOperatingp == tcurr)) { tcurrop = tcurr = mCurrentOperatingp->mNextp; } // remove the LLLinkNode mCurrentp = mCurrentOperatingp->mNextp; // move the node to the new list newlist->addData(mCurrentOperatingp); if (b_sort) newlist->bubbleSortList(); mCurrentOperatingp = mCurrentp; break; } mCurrentOperatingp = mCurrentOperatingp->mNextp; } // restore mCurrentp = tcurr; mCurrentOperatingp = tcurrop; return b_found; } // delete the Node at mCurentOperatingp // leave mCurrentp anf mCurentOperatingp on the next entry // return TRUE if found, FALSE if not found template <class DATA_TYPE> void LLLinkedList<DATA_TYPE>::deleteCurrentData() { if (mCurrentOperatingp) { // remove the node // if there is a next one, fix it if (mCurrentOperatingp->mNextp) { mCurrentOperatingp->mNextp->mPrevpp = mCurrentOperatingp->mPrevpp; } *(mCurrentOperatingp->mPrevpp) = mCurrentOperatingp->mNextp; // remove the LLLinkNode mCurrentp = mCurrentOperatingp->mNextp; mCurrentOperatingp->deleteData(); if (mCurrentOperatingp->mDatap) llerror("This is impossible!", 0); delete mCurrentOperatingp; mCurrentOperatingp = mCurrentp; mCount--; } } #endif