-// 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
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
bool
Element::empty() const {
- throwTypeError("empty() called on a non-list Element");
+ throwTypeError("empty() called on a non-container Element");
}
ConstElementPtr
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
//
}
}
+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) {
-// 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
#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 {
virtual bool setValue(const std::map<std::string, ConstElementPtr>& v);
//@}
-
-
// Other functions for specific subtypes
/// @name ListElement functions
virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
//@}
-
/// @name Factory functions
// TODO: should we move all factory functions to a different class
static ElementPtr createMap(const Position& pos = ZERO_POSITION());
//@}
-
/// @name Compound factory functions
/// @brief These functions will parse the given string (JSON)
/// @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.
///
class IntElement : public Element {
int64_t i;
-private:
-
public:
IntElement(int64_t v, const Position& pos = ZERO_POSITION())
: Element(integer, pos), i(v) { }
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 {
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;
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
/// 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
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:
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