From: Andrei Pavel Date: Thu, 15 Jul 2021 12:14:20 +0000 (+0300) Subject: [#1077] ListElement::sort() X-Git-Tag: Kea-1.9.10~55 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2f6c06c2b792372d2fc981fff33b0b7d8444c8b8;p=thirdparty%2Fkea.git [#1077] ListElement::sort() created to be used in NETCONF unit tests --- diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc index d644c75cfa..14a467dc9d 100644 --- a/src/lib/cc/data.cc +++ b/src/lib/cc/data.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2010-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2010-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -128,7 +128,7 @@ Element::setValue(const std::map&) { ConstElementPtr Element::get(const int) const { - throwTypeError("get(int) called on a non-list Element"); + throwTypeError("get(int) called on a non-container Element"); } ElementPtr @@ -158,7 +158,7 @@ Element::size() const { bool Element::empty() const { - throwTypeError("empty() called on a non-list Element"); + throwTypeError("empty() called on a non-container Element"); } ConstElementPtr @@ -215,6 +215,25 @@ bool operator!=(const Element& a, const Element& b) { return (!a.equals(b)); } +bool +operator<(Element const& a, Element const& b) { + if (a.getType() != b.getType()) { + isc_throw(BadValue, "cannot compare Elements of different types"); + } + switch (a.getType()) { + case Element::integer: + return a.intValue() < b.intValue(); + case Element::real: + return a.doubleValue() < b.doubleValue(); + case Element::boolean: + return b.boolValue() || !a.boolValue(); + case Element::string: + return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0; + } + isc_throw(BadValue, "cannot compare Elements of type " << + std::to_string(a.getType())); +} + // // factory functions // @@ -1002,6 +1021,42 @@ ListElement::equals(const Element& other) const { } } +void +ListElement::sort(std::string const& index /* = std::string() */) { + if (l.empty()) { + return; + } + + int const t(l.at(0)->getType()); + std::function comparator; + if (t == map) { + if (index.empty()) { + isc_throw(BadValue, "index required when sorting maps"); + } + comparator = [&](ElementPtr const& a, ElementPtr const& b) { + ConstElementPtr const& ai(a->get(index)); + ConstElementPtr const& bi(b->get(index)); + if (ai && bi) { + return *ai < *bi; + } + return true; + }; + } else if (t == list) { + // Nested lists. Not supported. + return; + } else { + // Assume scalars. + if (!index.empty()) { + isc_throw(BadValue, "index given when sorting scalars?"); + } + comparator = [&](ElementPtr const& a, ElementPtr const& b) { + return *a < *b; + }; + } + + std::sort(l.begin(), l.end(), comparator); +} + bool MapElement::equals(const Element& other) const { if (other.getType() == Element::map) { diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h index 4403b5179e..f5315f1839 100644 --- a/src/lib/cc/data.h +++ b/src/lib/cc/data.h @@ -1,4 +1,4 @@ -// Copyright (C) 2010-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2010-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,12 +7,16 @@ #ifndef ISC_DATA_H #define ISC_DATA_H 1 -#include +#include +#include +#include #include #include -#include + #include -#include + +#include + #include namespace isc { namespace data { @@ -263,8 +267,6 @@ public: virtual bool setValue(const std::map& v); //@} - - // Other functions for specific subtypes /// @name ListElement functions @@ -352,7 +354,6 @@ public: virtual bool find(const std::string& identifier, ConstElementPtr& t) const; //@} - /// @name Factory functions // TODO: should we move all factory functions to a different class @@ -403,7 +404,6 @@ public: static ElementPtr createMap(const Position& pos = ZERO_POSITION()); //@} - /// @name Compound factory functions /// @brief These functions will parse the given string (JSON) @@ -529,6 +529,50 @@ public: /// @return ElementPtr with the data that is parsed. static ElementPtr fromWire(const std::string& s); //@} + + /// @brief Remove all empty maps and lists from this Element and its + /// descendants. + void removeEmptyContainersRecursively() { + if (type_ == list || type_ == map) { + size_t s(size()); + for (size_t i = 0; i < s; ++i) { + // Get child. + ElementPtr child; + if (type_ == list) { + child = getNonConst(i); + } else if (type_ == map) { + std::string const key(get(i)->stringValue()); + // The ElementPtr - ConstElementPtr disparity between + // ListElement and MapElement is forcing a const cast here. + // It's undefined behavior to modify it after const casting. + // The options are limited. I've tried templating, moving + // this function from a member function to free-standing and + // taking the Element template as argument. I've tried + // making it a virtual function with overriden + // implementations in ListElement and MapElement. Nothing + // works. + child = boost::const_pointer_cast(get(key)); + } + + // Makes no sense to continue for non-container children. + if (child->getType() != list && child->getType() != map) { + continue; + } + + // Recurse if not empty. + if (!child->empty()){ + child->removeEmptyContainersRecursively(); + } + + // When returning from recursion, remove if empty. + if (child->empty()) { + remove(i); + --i; + --s; + } + } + } + } }; /// Notes: IntElement type is changed to int64_t. @@ -543,8 +587,6 @@ public: /// class IntElement : public Element { int64_t i; -private: - public: IntElement(int64_t v, const Position& pos = ZERO_POSITION()) : Element(integer, pos), i(v) { } @@ -641,6 +683,17 @@ public: size_t size() const { return (l.size()); } bool empty() const { return (l.empty()); } bool equals(const Element& other) const; + + /// @brief Sorts the elements inside the list. + /// + /// The list must contain elments of the same type. + /// Call with the key by which you want to sort when the list contains maps. + /// Nested lists are not supported. + /// Call without a parameter when sorting any other type. + /// + /// @param index the key by which you want to sort when the list contains + /// maps + void sort(std::string const& index = std::string()); }; class MapElement : public Element { @@ -668,6 +721,19 @@ public: auto found = m.find(s); return (found != m.end() ? found->second : ConstElementPtr()); } + + /// @brief Get the i-th element in the map. + /// + /// Useful when required to iterate with an index. + /// + /// @param i the position of the element you want to return + /// @return the element at position i + ConstElementPtr get(int const i) const override { + auto it(m.begin()); + std::advance(it, i); + return create(it->first); + } + using Element::set; void set(const std::string& key, ConstElementPtr value); using Element::remove; @@ -783,7 +849,6 @@ void prettyPrint(ConstElementPtr element, std::ostream& out, std::string prettyPrint(ConstElementPtr element, unsigned indent = 0, unsigned step = 2); -/// /// @brief Insert Element::Position as a string into stream. /// /// This operator converts the @c Element::Position into a string and @@ -796,7 +861,6 @@ std::string prettyPrint(ConstElementPtr element, /// parameter @c out after the insertion operation. std::ostream& operator<<(std::ostream& out, const Element::Position& pos); -/// /// @brief Insert the Element as a string into stream. /// /// This method converts the @c ElementPtr into a string with @@ -815,7 +879,11 @@ std::ostream& operator<<(std::ostream& out, const Element& e); bool operator==(const Element& a, const Element& b); bool operator!=(const Element& a, const Element& b); -} } +bool operator<(const Element& a, const Element& b); + +} // namespace data +} // namespace isc + #endif // ISC_DATA_H // Local Variables: diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc index fe84693e2c..34540259da 100644 --- a/src/lib/cc/tests/data_unittests.cc +++ b/src/lib/cc/tests/data_unittests.cc @@ -1390,4 +1390,47 @@ TEST(Element, empty) { l->remove(0); EXPECT_TRUE(l->empty()); } + +TEST(Element, sortIntegers) { + ElementPtr l(Element::fromJSON("[5, 7, 4, 2, 8, 6, 1, 9, 0, 3]")); + ElementPtr expected(Element::fromJSON("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortFloatingPoint) { + ElementPtr l(Element::fromJSON("[2.1, 3.2, 2.1, 2.2, 4.1, 3.2, 1.1, 4.2, 0.1, 1.2]")); + ElementPtr expected(Element::fromJSON("[0.1, 1.1, 1.2, 2.1, 2.1, 2.2, 3.2, 3.2, 4.1, 4.2]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortBooleans) { + ElementPtr l(Element::fromJSON("[false, true, false, true]")); + ElementPtr expected(Element::fromJSON("[false, false, true, true]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); } + +TEST(Element, sortStrings) { + ElementPtr l(Element::fromJSON(R"(["hello", "world", "lorem", "ipsum", "dolor", "sit", "amet"])")); + ElementPtr expected(Element::fromJSON(R"(["amet", "dolor", "hello", "ipsum", "lorem", "sit", "world"])")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortMaps) { + ElementPtr e1(Element::fromJSON(R"({"id": 1, "subnet": "10.0.1.0/24"})")); + ElementPtr e2(Element::fromJSON(R"({"id": 2, "subnet": "10.0.1.0/24"})")); + ElementPtr l(Element::createList()); + l->add(e2); + l->add(e1); + EXPECT_EQ(*l->get(0), *e2); + EXPECT_EQ(*l->get(1), *e1); + boost::dynamic_pointer_cast(l)->sort("id"); + ASSERT_EQ(l->size(), 2); + EXPECT_EQ(*l->get(0), *e1); + EXPECT_EQ(*l->get(1), *e2); +} + +} // namespace