]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1077] ListElement::sort()
authorAndrei Pavel <andrei@isc.org>
Thu, 15 Jul 2021 12:14:20 +0000 (15:14 +0300)
committerTomek Mrugalski <tomek@isc.org>
Fri, 23 Jul 2021 10:45:36 +0000 (10:45 +0000)
created to be used in NETCONF unit tests

src/lib/cc/data.cc
src/lib/cc/data.h
src/lib/cc/tests/data_unittests.cc

index d644c75cfad78d211a9d6bb93f875837b4faf6d2..14a467dc9d2c10d8724598716520b1537d3de187 100644 (file)
@@ -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<std::string, ConstElementPtr>&) {
 
 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<bool(ElementPtr, ElementPtr)> 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) {
index 4403b5179e94541b6e929bac738447ff523341fa..f5315f18392ae18ab2d264f34f10c1f54b8388d7 100644 (file)
@@ -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 <stdint.h>
+#include <iostream>
+#include <map>
+#include <stdexcept>
 #include <string>
 #include <vector>
-#include <map>
+
 #include <boost/shared_ptr.hpp>
-#include <stdexcept>
+
+#include <stdint.h>
+
 #include <exceptions/exceptions.h>
 
 namespace isc { namespace data {
@@ -263,8 +267,6 @@ public:
     virtual bool setValue(const std::map<std::string, ConstElementPtr>& 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<Element>(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:
index fe84693e2c2e2bd6c1e65319ab5cc889ea4707f4..34540259da97475c1588b8b5ec9a1e2f726d6db2 100644 (file)
@@ -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<ListElement>(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<ListElement>(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<ListElement>(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<ListElement>(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<ListElement>(l)->sort("id");
+    ASSERT_EQ(l->size(), 2);
+    EXPECT_EQ(*l->get(0), *e1);
+    EXPECT_EQ(*l->get(1), *e2);
+}
+
+}  // namespace