2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[#1077] ListElement::sort()

created to be used in NETCONF unit tests
This commit is contained in:
Andrei Pavel
2021-07-15 15:14:20 +03:00
committed by Tomek Mrugalski
parent 8a733f3074
commit 2f6c06c2b7
3 changed files with 182 additions and 16 deletions

View 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) {

View 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:

View 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