From 5ebd885996f9c022590f28e0057b56d4db0a7e08 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 29 Jun 2020 22:00:20 -0700 Subject: [PATCH] [linked-list] add new template {Contains/Find/Remove}Matching() methods (#5174) This commit adds new methods in `LinkedList` to search in the list for an entry matching a given entry indicator (specified with a template `Indicator` type). To use these methods, users are expected to provide method `bool Matches(const Indicator &aIndicator) const` on the entry `Type` itself (which is then used to match each entry in the list with the indicator). This commit also changes the linked-list methods to use `Find()` internally. Finally it updates the linked-list unit test to cover behavior of the newly added methods. --- src/core/common/linked_list.hpp | 200 ++++++++++++++++++++++++-------- tests/unit/test_linked_list.cpp | 76 +++++++++++- 2 files changed, 220 insertions(+), 56 deletions(-) diff --git a/src/core/common/linked_list.hpp b/src/core/common/linked_list.hpp index f5a97fcc5..166405f93 100644 --- a/src/core/common/linked_list.hpp +++ b/src/core/common/linked_list.hpp @@ -113,7 +113,7 @@ public: /** * This method returns the entry at the head of the linked list * - * @returns Pointer to the entry at the head of the linked list, or nullptr if list is empty. + * @returns Pointer to the entry at the head of the linked list, or nullptr if the list is empty. * */ Type *GetHead(void) { return mHead; } @@ -121,7 +121,7 @@ public: /** * This method returns the entry at the head of the linked list. * - * @returns Pointer to the entry at the head of the linked list, or nullptr if list is empty. + * @returns Pointer to the entry at the head of the linked list, or nullptr if the list is empty. * */ const Type *GetHead(void) const { return mHead; } @@ -179,7 +179,7 @@ public: * * @note This method does not change the popped entry itself, i.e., the popped entry next pointer stays as before. * - * @returns The entry that was popped if list is not empty, or nullptr if list is empty. + * @returns The entry that was popped if the list is not empty, or nullptr if the list is empty. * */ Type *Pop(void) @@ -200,7 +200,7 @@ public: * @note This method does not change the popped entry itself, i.e., the popped entry next pointer stays as before. * * @param[in] aPrevEntry A pointer to a previous entry. If it is not nullptr the entry after this will be popped, - * otherwise (if it is nullptr) the entry at head of the list is popped. + * otherwise (if it is nullptr) the entry at the head of the list is popped. * * @returns Pointer to the entry that was popped, or nullptr if there is no entry to pop. * @@ -237,18 +237,31 @@ public: */ bool Contains(const Type &aEntry) const { - bool contains = false; + const Type *prev; - for (Type *cur = mHead; cur != nullptr; cur = cur->GetNext()) - { - if (cur == &aEntry) - { - contains = true; - break; - } - } + return Find(aEntry, prev) == OT_ERROR_NONE; + } - return contains; + /** + * This template method indicates whether the linked list contains an entry matching a given entry indicator. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against entries + * in the list. To check that an entry matches the given indicator, the `Matches()` method is invoked on each + * `Type` entry in the list. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @param[in] aIndicator An entry indicator to match against entries in the list. + * + * @retval TRUE The linked list contains an entry matching @p aIndicator. + * @retval FALSE The linked list contains no entry matching @p aIndicator. + * + */ + template bool ContainsMatching(const Indicator &aIndicator) const + { + const Type *prev; + + return FindMatching(aIndicator, prev) != nullptr; } /** @@ -279,7 +292,7 @@ public: /** * This method removes an entry from the linked list. * - * @note This method does not change the removed entry @p aEntry itself (it is `const`), i.e., the entry next + * @note This method does not change the removed entry @p aEntry itself (it is `const`), i.e., the entry next * pointer of @p aEntry stays as before. * * @param[in] aEntry A reference to an entry to remove. @@ -289,24 +302,73 @@ public: * */ otError Remove(const Type &aEntry) + { + Type * prev; + otError error = Find(aEntry, prev); + + if (error == OT_ERROR_NONE) + { + PopAfter(prev); + } + + return error; + } + + /** + * This template method removes an entry matching a given entry indicator from the linked list. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against entries + * in the list. To check that an entry matches the given indicator, the `Matches()` method is invoked on each + * `Type` entry in the list. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @note This method does not change the removed entry itself (which is returned in case of success), i.e., the + * entry next pointer stays as before. + * + * + * @param[in] aIndicator An entry indicator to match against entries in the list. + * + * @returns A pointer to the removed matching entry if one could be found, or nullptr if no matching entry is found. + * + */ + template Type *RemoveMatching(const Indicator &aIndicator) + { + Type *prev; + Type *entry = FindMatching(aIndicator, prev); + + if (entry != nullptr) + { + PopAfter(prev); + } + + return entry; + } + + /** + * This method searches within the linked list to find an entry and if found returns a pointer to previous entry. + * + * @param[in] aEntry A reference to an entry to find. + * @param[out] aPrevEntry A pointer to output the previous entry on success (when @p aEntry is found in the list). + * @p aPrevEntry is set to nullptr if @p aEntry is the head of the list. Otherwise it is + * updated to point to the previous entry before @p aEntry in the list. + * + * @retval OT_ERROR_NONE The entry was found in the list and @p aPrevEntry was updated successfully. + * @retval OT_ERROR_NOT_FOUND The entry was not found in the list. + * + */ + otError Find(const Type &aEntry, const Type *&aPrevEntry) const { otError error = OT_ERROR_NOT_FOUND; - if (mHead == &aEntry) + aPrevEntry = nullptr; + + for (const Type *entry = mHead; entry != nullptr; aPrevEntry = entry, entry = entry->GetNext()) { - Pop(); - error = OT_ERROR_NONE; - } - else if (mHead != nullptr) - { - for (Type *cur = mHead; cur->GetNext() != nullptr; cur = cur->GetNext()) + if (entry == &aEntry) { - if (cur->GetNext() == &aEntry) - { - cur->SetNext(cur->GetNext()->GetNext()); - error = OT_ERROR_NONE; - break; - } + error = OT_ERROR_NONE; + break; } } @@ -317,43 +379,81 @@ public: * This method searches within the linked list to find an entry and if found returns a pointer to previous entry. * * @param[in] aEntry A reference to an entry to find. - * @param[out] aPrevEntry A pointer to output previous entry on success (when @p aEntry is found is the list). - * @p aPrevEntry is set to nullptr if the @p aEntry is head of the list. Otherwise it - * is updated to point to previous entry before @p aEntry in the list. + * @param[out] aPrevEntry A pointer to output the previous entry on success (when @p aEntry is found in the list). + * @p aPrevEntry is set to nullptr if @p aEntry is the head of the list. Otherwise it is + * updated to point to the previous entry before @p aEntry in the list. * * @retval OT_ERROR_NONE The entry was found in the list and @p aPrevEntry was updated successfully. * @retval OT_ERROR_NOT_FOUND The entry was not found in the list. * */ - otError Find(const Type &aEntry, Type *&aPrevEntry) const + otError Find(const Type &aEntry, Type *&aPrevEntry) { - otError error = OT_ERROR_NOT_FOUND; + return const_cast(this)->Find(aEntry, const_cast(aPrevEntry)); + } - if (mHead == &aEntry) + /** + * This template method searches within the linked list to find an entry matching a given indicator. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against entries + * in the list. To check that an entry matches the given indicator, the `Matches()` method is invoked on each + * `Type` entry in the list. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @param[in] aIndicator An indicator to match with entries in the list.. + * @param[out] aPrevEntry A pointer to output the previous entry on success (when a match is found in the list). + * @p aPrevEntry is set to nullptr if the matching entry is the head of the list. Otherwise + * it is updated to point to the previous entry before the matching entry in the list. + * + * @returns A pointer to the matching entry if one is found, or nullptr if no matching entry was found. + * + */ + template const Type *FindMatching(const Inidcator &aIndicator, const Type *&aPrevEntry) const + { + const Type *entry; + + aPrevEntry = nullptr; + + for (entry = mHead; entry != nullptr; aPrevEntry = entry, entry = entry->GetNext()) { - aPrevEntry = nullptr; - error = OT_ERROR_NONE; - } - else if (mHead != nullptr) - { - for (Type *cur = mHead; cur->GetNext() != nullptr; cur = cur->GetNext()) + if (entry->Matches(aIndicator)) { - if (cur->GetNext() == &aEntry) - { - aPrevEntry = cur; - error = OT_ERROR_NONE; - break; - } + break; } } - return error; + return entry; + } + + /** + * This template method searches within the linked list to find an entry matching a given indicator, and if found + * returns a pointer to its previous entry in the list. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against entries + * in the list. To check that an entry matches the given indicator, the `Matches()` method is invoked on each + * `Type` entry in the list. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @param[in] aIndicator An indicator to match with entries in the list.. + * @param[out] aPrevEntry A pointer to output the previous entry on success (when a match is found in the list). + * @p aPrevEntry is set to nullptr if the matching entry is the head of the list. Otherwise + * it is updated to point to the previous entry before the matching entry in the list. + * + * @returns A pointer to the matching entry if one is found, or nullptr if no matching entry was found. + * + */ + template Type *FindMatching(const Inidcator &aIndicator, Type *&aPrevEntry) + { + return const_cast( + const_cast(this)->FindMatching(aIndicator, const_cast(aPrevEntry))); } /** * This method returns the tail of the linked list (i.e., the last entry in the list). * - * @returns A pointer to tail entry in the linked list or nullptr if list is empty. + * @returns A pointer to the tail entry in the linked list or nullptr if the list is empty. * */ const Type *GetTail(void) const @@ -374,10 +474,10 @@ public: /** * This method returns the tail of the linked list (i.e., the last entry in the list). * - * @returns A pointer to tail entry in the linked list or nullptr if list is empty. + * @returns A pointer to the tail entry in the linked list or nullptr if the list is empty. * */ - Type *GetTail(void) { return const_cast(const_cast *>(this)->GetTail()); } + Type *GetTail(void) { return const_cast(const_cast(this)->GetTail()); } private: Type *mHead; diff --git a/tests/unit/test_linked_list.cpp b/tests/unit/test_linked_list.cpp index d11287f81..5664a00c2 100644 --- a/tests/unit/test_linked_list.cpp +++ b/tests/unit/test_linked_list.cpp @@ -27,6 +27,7 @@ */ #include +#include #include "test_platform.h" @@ -45,29 +46,52 @@ struct EntryBase struct Entry : public EntryBase, ot::LinkedListEntry { +public: + Entry(const char *aName, uint16_t aId) + : mName(aName) + , mId(aId) + { + } + + const char *GetName(void) const { return mName; } + uint16_t GetId(void) const { return mId; } + bool Matches(const char *aName) const { return strcmp(mName, aName) == 0; } + bool Matches(uint16_t aId) const { return mId == aId; } + +private: + const char *mName; + uint16_t mId; }; // This function verifies the content of the linked list matches a given list of entries. void VerifyLinkedListContent(const ot::LinkedList *aList, ...) { - va_list args; - Entry * argEntry; - Entry * argPrev = nullptr; + va_list args; + Entry * argEntry; + Entry * argPrev = nullptr; + const Entry *prev; + uint16_t unusedId = 100; va_start(args, aList); for (const Entry *entry = aList->GetHead(); entry; entry = entry->GetNext()) { - Entry *prev; - argEntry = va_arg(args, Entry *); VerifyOrQuit(argEntry != nullptr, "List contains more entries than expected"); VerifyOrQuit(argEntry == entry, "List does not contain the same entry"); VerifyOrQuit(aList->Contains(*argEntry), "List::Contains() failed"); + VerifyOrQuit(aList->ContainsMatching(argEntry->GetName()), "List::ContainsMatching() failed"); + VerifyOrQuit(aList->ContainsMatching(argEntry->GetId()), "List::ContainsMatching() failed"); SuccessOrQuit(aList->Find(*argEntry, prev), "List::Find() failed"); VerifyOrQuit(prev == argPrev, "List::Find() returned prev entry is incorrect"); + VerifyOrQuit(aList->FindMatching(argEntry->GetName(), prev) == argEntry, "List::FindMatching() failed"); + VerifyOrQuit(prev == argPrev, "List::FindMatching() returned prev entry is incorrect"); + + VerifyOrQuit(aList->FindMatching(argEntry->GetId(), prev) == argEntry, "List::FindMatching() failed"); + VerifyOrQuit(prev == argPrev, "List::FindMatching() returned prev entry is incorrect"); + argPrev = argEntry; } @@ -75,11 +99,19 @@ void VerifyLinkedListContent(const ot::LinkedList *aList, ...) VerifyOrQuit(argEntry == nullptr, "List contains less entries than expected"); VerifyOrQuit(aList->GetTail() == argPrev, "List::GetTail() failed"); + + VerifyOrQuit(!aList->ContainsMatching("none"), "List::ContainsMatching() succeeded for a missing entry"); + VerifyOrQuit(!aList->ContainsMatching(unusedId), "List::ContainsMatching() succeeded for a missing entry"); + + VerifyOrQuit(aList->FindMatching("none", prev) == nullptr, + "LinkedList::FindMatching() succeeded for a missing entry"); + VerifyOrQuit(aList->FindMatching(unusedId, prev) == nullptr, + "LinkedList::FindMatching() succeeded for a missing entry"); } void TestLinkedList(void) { - Entry a, b, c, d, e; + Entry a("a", 1), b("b", 2), c("c", 3), d("d", 4), e("e", 5); Entry * prev; ot::LinkedList list; @@ -117,6 +149,17 @@ void TestLinkedList(void) VerifyLinkedListContent(&list, &d, &c, &b, &a, nullptr); VerifyOrQuit(list.Find(e, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry"); + VerifyOrQuit(list.FindMatching(d.GetName(), prev) == &d, "List::FindMatching() failed"); + VerifyOrQuit(prev == nullptr, "List::FindMatching() failed"); + VerifyOrQuit(list.FindMatching(c.GetId(), prev) == &c, "List::FindMatching() failed"); + VerifyOrQuit(prev == &d, "List::FindMatching() failed"); + VerifyOrQuit(list.FindMatching(b.GetName(), prev) == &b, "List::FindMatching() failed"); + VerifyOrQuit(prev == &c, "List::FindMatching() failed"); + VerifyOrQuit(list.FindMatching(a.GetId(), prev) == &a, "List::FindMatching() failed"); + VerifyOrQuit(prev == &b, "List::FindMatching() failed"); + VerifyOrQuit(list.FindMatching(e.GetId(), prev) == nullptr, "List::FindMatching() succeeded for a missing entry"); + VerifyOrQuit(list.FindMatching(e.GetName(), prev) == nullptr, "List::FindMatching() succeeded for a missing entry"); + list.SetHead(&e); VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr); @@ -161,11 +204,32 @@ void TestLinkedList(void) VerifyOrQuit(list.PopAfter(nullptr) == &a, "LinkedList::PopAfter() failed"); VerifyLinkedListContent(&list, &d, &b, &c, nullptr); + list.Push(e); + list.Push(a); + VerifyLinkedListContent(&list, &a, &e, &d, &b, &c, nullptr); + + VerifyOrQuit(list.RemoveMatching(a.GetName()) == &a, "LinkedList::RemoveMatching() failed"); + VerifyLinkedListContent(&list, &e, &d, &b, &c, nullptr); + + VerifyOrQuit(list.RemoveMatching(c.GetId()) == &c, "LinkedList::RemoveMatching() failed"); + VerifyLinkedListContent(&list, &e, &d, &b, nullptr); + + VerifyOrQuit(list.RemoveMatching(c.GetId()) == nullptr, "LinkedList::RemoveMatching() succeeded for missing entry"); + VerifyOrQuit(list.RemoveMatching(a.GetName()) == nullptr, + "LinkedList::RemoveMatching() succeeded for missing entry"); + + VerifyOrQuit(list.RemoveMatching(d.GetId()) == &d, "LinkedList::RemoveMatching() failed"); + VerifyLinkedListContent(&list, &e, &b, nullptr); + list.Clear(); VerifyOrQuit(list.IsEmpty(), "LinkedList::IsEmpty() failed after Clear()"); VerifyOrQuit(list.PopAfter(nullptr) == nullptr, "LinkedList::PopAfter() failed"); VerifyLinkedListContent(&list, nullptr); VerifyOrQuit(list.Find(a, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry"); + VerifyOrQuit(list.FindMatching(b.GetName(), prev) == nullptr, "LinkedList::FindMatching() succeeded when empty"); + VerifyOrQuit(list.FindMatching(c.GetId(), prev) == nullptr, "LinkedList::FindMatching() succeeded when empty"); + VerifyOrQuit(list.RemoveMatching(a.GetName()) == nullptr, "LinkedList::RemoveMatching() succeeded when empty"); + VerifyOrQuit(list.Remove(a) == OT_ERROR_NOT_FOUND, "LinkedList::Remove() succeeded when empty"); } int main(void)