Kea 3.1.8
data.cc
Go to the documentation of this file.
1// Copyright (C) 2010-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <cc/data.h>
10#include <util/bigints.h>
11
12#include <cstring>
13#include <cassert>
14#include <climits>
15#include <list>
16#include <map>
17#include <cstdio>
18#include <iostream>
19#include <iomanip>
20#include <set>
21#include <string>
22#include <sstream>
23#include <fstream>
24#include <cerrno>
25
26#include <boost/lexical_cast.hpp>
27
28#include <cmath>
29
30using namespace std;
31
33
34namespace {
35const char* const WHITESPACE = " \b\f\n\r\t";
36} // end anonymous namespace
37
38namespace isc {
39namespace data {
40
41constexpr unsigned Element::MAX_NESTING_LEVEL;
42
43std::string
45 std::ostringstream ss;
46 ss << file_ << ":" << line_ << ":" << pos_;
47 return (ss.str());
48}
49
50std::ostream&
51operator<<(std::ostream& out, const Element::Position& pos) {
52 out << pos.str();
53 return (out);
54}
55
56void
58 if (level <= 0) {
59 // Cycles are by definition not empty so no need to throw.
60 return;
61 }
62 if (type_ == list || type_ == map) {
63 size_t s(size());
64 for (size_t i = 0; i < s; ++i) {
65 // Get child.
66 ElementPtr child;
67 if (type_ == list) {
68 child = getNonConst(i);
69 } else if (type_ == map) {
70 std::string const key(get(i)->stringValue());
71 // The ElementPtr - ConstElementPtr disparity between
72 // ListElement and MapElement is forcing a const cast here.
73 // It's undefined behavior to modify it after const casting.
74 // The options are limited. I've tried templating, moving
75 // this function from a member function to free-standing and
76 // taking the Element template as argument. I've tried
77 // making it a virtual function with overridden
78 // implementations in ListElement and MapElement. Nothing
79 // works.
80 child = boost::const_pointer_cast<Element>(get(key));
81 }
82
83 // Makes no sense to continue for non-container children.
84 if (child->getType() != list && child->getType() != map) {
85 continue;
86 }
87
88 // Recurse if not empty.
89 if (!child->empty()){
90 child->removeEmptyContainersRecursively(level - 1);
91 }
92
93 // When returning from recursion, remove if empty.
94 if (child->empty()) {
95 remove(i);
96 --i;
97 --s;
98 }
99 }
100 }
101}
102
103std::string
105 std::stringstream ss;
106 toJSON(ss);
107 return (ss.str());
108}
109
110std::string
112 std::stringstream ss;
113 toJSON(ss);
114 return (ss.str());
115}
116
117void
118Element::toWire(std::ostream& ss) const {
119 toJSON(ss);
120}
121
122bool
123Element::getValue(int64_t&) const {
124 return (false);
125}
126
127bool
128Element::getValue(double&) const {
129 return (false);
130}
131
132bool
133Element::getValue(bool&) const {
134 return (false);
135}
136
137bool
138Element::getValue(std::string&) const {
139 return (false);
140}
141
142bool
143Element::getValue(std::vector<ElementPtr>&) const {
144 return (false);
145}
146
147bool
148Element::getValue(std::map<std::string, ConstElementPtr>&) const {
149 return (false);
150}
151
152bool
153Element::setValue(const long long int) {
154 return (false);
155}
156
157bool
159 return (false);
160}
161
162bool
163Element::setValue(const double) {
164 return (false);
165}
166
167bool
168Element::setValue(const bool) {
169 return (false);
170}
171
172bool
173Element::setValue(const std::string&) {
174 return (false);
175}
176
177bool
178Element::setValue(const std::vector<ElementPtr>&) {
179 return (false);
180}
181
182bool
183Element::setValue(const std::map<std::string, ConstElementPtr>&) {
184 return (false);
185}
186
188Element::get(const int) const {
189 throwTypeError("get(int) called on a non-container Element");
190}
191
193Element::getNonConst(const int) const {
194 throwTypeError("get(int) called on a non-container Element");
195}
196
197void
198Element::set(const size_t, ElementPtr) {
199 throwTypeError("set(int, element) called on a non-list Element");
200}
201
202void
204 throwTypeError("add() called on a non-list Element");
205}
206
207void
208Element::remove(const int) {
209 throwTypeError("remove(int) called on a non-container Element");
210}
211
212size_t
214 throwTypeError("size() called on a non-list Element");
215}
216
217bool
219 throwTypeError("empty() called on a non-container Element");
220}
221
223Element::get(const std::string&) const {
224 throwTypeError("get(string) called on a non-map Element");
225}
226
227void
228Element::set(const std::string&, ConstElementPtr) {
229 throwTypeError("set(name, element) called on a non-map Element");
230}
231
232void
233Element::remove(const std::string&) {
234 throwTypeError("remove(string) called on a non-map Element");
235}
236
237bool
238Element::contains(const std::string&) const {
239 throwTypeError("contains(string) called on a non-map Element");
240}
241
243Element::find(const std::string&) const {
244 throwTypeError("find(string) called on a non-map Element");
245}
246
247bool
248Element::find(const std::string&, ConstElementPtr&) const {
249 return (false);
250}
251
252namespace {
253inline void
254throwJSONError(const std::string& error, const std::string& file, int line,
255 int pos) {
256 std::stringstream ss;
257 ss << error << " in " + file + ":" << line << ":" << pos;
258 isc_throw(JSONError, ss.str());
259}
260} // end anonymous namespace
261
262std::ostream&
263operator<<(std::ostream& out, const Element& e) {
264 return (out << e.str());
265}
266
267bool
268operator==(const Element& a, const Element& b) {
269 return (a.equals(b));
270}
271
272bool operator!=(const Element& a, const Element& b) {
273 return (!a.equals(b));
274}
275
276bool
277operator<(Element const& a, Element const& b) {
278 if (a.getType() != b.getType()) {
279 isc_throw(BadValue, "cannot compare Elements of different types");
280 }
281 switch (a.getType()) {
282 case Element::integer:
283 return a.intValue() < b.intValue();
284 case Element::real:
285 return a.doubleValue() < b.doubleValue();
286 case Element::boolean:
287 return b.boolValue() || !a.boolValue();
288 case Element::string:
289 return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0;
290 default:
291 isc_throw(BadValue, "cannot compare Elements of type " << to_string(a.getType()));
292 }
293}
294
295//
296// factory functions
297//
300 return (ElementPtr(new NullElement(pos)));
301}
302
304Element::create(const long long int i, const Position& pos) {
305 return (ElementPtr(new IntElement(static_cast<int64_t>(i), pos)));
306}
307
310 return (ElementPtr(new BigIntElement(i, pos)));
311}
312
314Element::create(const int i, const Position& pos) {
315 return (create(static_cast<long long int>(i), pos));
316}
317
319Element::create(const long int i, const Position& pos) {
320 return (create(static_cast<long long int>(i), pos));
321}
322
324Element::create(const uint32_t i, const Position& pos) {
325 return (create(static_cast<long long int>(i), pos));
326}
327
329Element::create(const double d, const Position& pos) {
330 return (ElementPtr(new DoubleElement(d, pos)));
332
334Element::create(const bool b, const Position& pos) {
335 return (ElementPtr(new BoolElement(b, pos)));
336}
339Element::create(const std::string& s, const Position& pos) {
340 return (ElementPtr(new StringElement(s, pos)));
341}
342
344Element::create(const char *s, const Position& pos) {
345 return (create(std::string(s), pos));
346}
347
350 return (ElementPtr(new ListElement(pos)));
351}
352
355 return (ElementPtr(new MapElement(pos)));
356}
357
358
359//
360// helper functions for fromJSON factory
361//
362namespace {
363bool
364charIn(const int c, const char* chars) {
365 const size_t chars_len = std::strlen(chars);
366 for (size_t i = 0; i < chars_len; ++i) {
367 if (chars[i] == c) {
368 return (true);
369 }
370 }
371 return (false);
373
374void
375skipChars(std::istream& in, const char* chars, int& line, int& pos) {
376 int c = in.peek();
377 while (charIn(c, chars) && c != EOF) {
378 if (c == '\n') {
379 ++line;
380 pos = 1;
381 } else {
382 ++pos;
383 }
384 in.ignore();
385 c = in.peek();
386 }
387}
388
389// skip on the input stream to one of the characters in chars
390// if another character is found this function throws JSONError
391// unless that character is specified in the optional may_skip
392//
393// It returns the found character (as an int value).
394int
395skipTo(std::istream& in, const std::string& file, int& line, int& pos,
396 const char* chars, const char* may_skip="") {
397 int c = in.get();
398 ++pos;
399 while (c != EOF) {
400 if (c == '\n') {
401 pos = 1;
402 ++line;
403 }
404 if (charIn(c, may_skip)) {
405 c = in.get();
406 ++pos;
407 } else if (charIn(c, chars)) {
408 while (charIn(in.peek(), may_skip)) {
409 if (in.peek() == '\n') {
410 pos = 1;
411 ++line;
412 } else {
413 ++pos;
414 }
415 in.ignore();
416 }
417 return (c);
418 } else {
419 throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
420 }
421 }
422 throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
423 return (c); // shouldn't reach here, but some compilers require it
424}
425
426// TODO: Should we check for all other official escapes here (and
427// error on the rest)?
428std::string
429strFromStringstream(std::istream& in, const std::string& file,
430 const int line, int& pos) {
431 std::stringstream ss;
432 int c = in.get();
433 ++pos;
434 if (c == '"') {
435 c = in.get();
436 ++pos;
437 } else {
438 throwJSONError("String expected", file, line, pos);
439 }
440
441 while (c != EOF && c != '"') {
442 if (c == '\\') {
443 // see the spec for allowed escape characters
444 int d;
445 switch (in.peek()) {
446 case '"':
447 c = '"';
448 break;
449 case '/':
450 c = '/';
451 break;
452 case '\\':
453 c = '\\';
454 break;
455 case 'b':
456 c = '\b';
457 break;
458 case 'f':
459 c = '\f';
460 break;
461 case 'n':
462 c = '\n';
463 break;
464 case 'r':
465 c = '\r';
466 break;
467 case 't':
468 c = '\t';
469 break;
470 case 'u':
471 // skip first 0
472 in.ignore();
473 ++pos;
474 c = in.peek();
475 if (c != '0') {
476 throwJSONError("Unsupported unicode escape", file, line, pos);
477 }
478 // skip second 0
479 in.ignore();
480 ++pos;
481 c = in.peek();
482 if (c != '0') {
483 throwJSONError("Unsupported unicode escape", file, line, pos - 2);
485 // get first digit
486 in.ignore();
487 ++pos;
488 d = in.peek();
489 if ((d >= '0') && (d <= '9')) {
490 c = (d - '0') << 4;
491 } else if ((d >= 'A') && (d <= 'F')) {
492 c = (d - 'A' + 10) << 4;
493 } else if ((d >= 'a') && (d <= 'f')) {
494 c = (d - 'a' + 10) << 4;
495 } else {
496 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3);
497 }
498 // get second digit
499 in.ignore();
500 ++pos;
501 d = in.peek();
502 if ((d >= '0') && (d <= '9')) {
503 c |= d - '0';
504 } else if ((d >= 'A') && (d <= 'F')) {
505 c |= d - 'A' + 10;
506 } else if ((d >= 'a') && (d <= 'f')) {
507 c |= d - 'a' + 10;
508 } else {
509 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4);
510 }
511 break;
512 default:
513 throwJSONError("Bad escape", file, line, pos);
514 }
515 // drop the escaped char
516 in.ignore();
517 ++pos;
518 }
519 ss.put(c);
520 c = in.get();
521 ++pos;
522 }
523 if (c == EOF) {
524 throwJSONError("Unterminated string", file, line, pos);
525 }
526 return (ss.str());
527}
528
529std::string
530wordFromStringstream(std::istream& in, int& pos) {
531 std::stringstream ss;
532 while (isalpha(in.peek())) {
533 ss << (char) in.get();
534 }
535 pos += ss.str().size();
536 return (ss.str());
537}
538
539std::string
540numberFromStringstream(std::istream& in, int& pos) {
541 std::stringstream ss;
542 while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
543 in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
544 ss << (char) in.get();
545 }
546 pos += ss.str().size();
547 return (ss.str());
548}
549
550// Should we change from IntElement and DoubleElement to NumberElement
551// that can also hold an e value? (and have specific getters if the
552// value is larger than an int can handle)
553//
554// At the moment of writing, the only way that the code flow can reach the
555// int128_t cast, and not throw, is by retrieving one of the few bigint
556// statistics using this method so the cast to int128_t can be removed
557// as there is no deserialization of bigints required, although the only
558// benefit would be better performance for error cases, so it's arguable.
560fromStringstreamNumber(std::istream& in, const std::string& file,
561 const int line, int& pos) {
562 // Remember position where the value starts. It will be set in the
563 // Position structure of the Element to be created.
564 const uint32_t start_pos = pos;
565 // This will move the pos to the end of the value.
566 const std::string number = numberFromStringstream(in, pos);
567
568 // Is it a double?
569 if (number.find_first_of(".eE") < number.size()) {
570 try {
571 return (Element::create(boost::lexical_cast<double>(number),
572 Element::Position(file, line, start_pos)));
573 } catch (const boost::bad_lexical_cast& exception) {
574 throwJSONError("Number overflow while trying to cast '" + number +
575 "' to double: " + exception.what(),
576 file, line, start_pos);
577 }
578 }
579
580 // Is it an integer?
581 // Catch leading zeros: raise an error as logging is not available.
582 if (((number.size() > 1) && (number[0] == '0')) ||
583 ((number.size() > 2) && (number[0] == '-') && (number[1] == '0'))) {
584 throwJSONError("Illegal leading zeros in '" + number + "'",
585 file, line, start_pos);
586 }
587 // Catch leading plus: raise an error as logging is not available.
588 if ((number.size() > 1) && (number[0] == '+')) {
589 throwJSONError("Illegal leading plus in '" + number + "'",
590 file, line, start_pos);
591 }
592 try {
593 return (Element::create(boost::lexical_cast<int64_t>(number),
594 Element::Position(file, line, start_pos)));
595 } catch (const boost::bad_lexical_cast& exception64) {
596 // Is it a big integer?
597 try {
598 return (Element::create(int128_t(number),
599 Element::Position(file, line, start_pos)));
600 } catch (overflow_error const& exception128) {
601 throwJSONError("Number overflow while trying to cast '" + number +
602 "' to int64 and subsequently to int128: " +
603 exception64.what() + ", " + exception128.what(),
604 file, line, start_pos);
605 }
606 }
607 return (ElementPtr());
608}
609
611fromStringstreamBool(std::istream& in, const std::string& file,
612 const int line, int& pos) {
613 // Remember position where the value starts. It will be set in the
614 // Position structure of the Element to be created.
615 const uint32_t start_pos = pos;
616 // This will move the pos to the end of the value.
617 const std::string word = wordFromStringstream(in, pos);
618
619 if (word == "true") {
620 return (Element::create(true, Element::Position(file, line,
621 start_pos)));
622 } else if (word == "false") {
623 return (Element::create(false, Element::Position(file, line,
624 start_pos)));
625 } else {
626 throwJSONError(std::string("Bad boolean value: ") + word, file,
627 line, start_pos);
628 }
629 return (ElementPtr());
630}
631
633fromStringstreamNull(std::istream& in, const std::string& file,
634 const int line, int& pos) {
635 // Remember position where the value starts. It will be set in the
636 // Position structure of the Element to be created.
637 const uint32_t start_pos = pos;
638 // This will move the pos to the end of the value.
639 const std::string word = wordFromStringstream(in, pos);
640 if (word == "null") {
641 return (Element::create(Element::Position(file, line, start_pos)));
642 } else {
643 throwJSONError(std::string("Bad null value: ") + word, file,
644 line, start_pos);
645 return (ElementPtr());
646 }
647}
648
650fromStringstreamString(std::istream& in, const std::string& file, int& line,
651 int& pos) {
652 // Remember position where the value starts. It will be set in the
653 // Position structure of the Element to be created.
654 const uint32_t start_pos = pos;
655 // This will move the pos to the end of the value.
656 const std::string string_value = strFromStringstream(in, file, line, pos);
657 return (Element::create(string_value, Element::Position(file, line,
658 start_pos)));
659}
660
662fromStringstreamList(std::istream& in, const std::string& file, int& line,
663 int& pos, unsigned level) {
664 if (level == 0) {
665 isc_throw(JSONError, "fromJSON elements nested too deeply");
666 }
667 int c = 0;
668 ElementPtr list = Element::createList(Element::Position(file, line, pos));
669 ElementPtr cur_list_element;
670
671 skipChars(in, WHITESPACE, line, pos);
672 while (c != EOF && c != ']') {
673 if (in.peek() != ']') {
674 cur_list_element =
675 Element::fromJSON(in, file, line, pos, level - 1);
676 list->add(cur_list_element);
677 c = skipTo(in, file, line, pos, ",]", WHITESPACE);
678 } else {
679 c = in.get();
680 ++pos;
681 }
682 }
683 return (list);
684}
685
687fromStringstreamMap(std::istream& in, const std::string& file, int& line,
688 int& pos, unsigned level) {
689 if (level == 0) {
690 isc_throw(JSONError, "fromJSON elements nested too deeply");
691 }
692 ElementPtr map = Element::createMap(Element::Position(file, line, pos));
693 skipChars(in, WHITESPACE, line, pos);
694 int c = in.peek();
695 if (c == EOF) {
696 throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
697 } else if (c == '}') {
698 // empty map, skip closing curly
699 in.ignore();
700 } else {
701 while (c != EOF && c != '}') {
702 std::string key = strFromStringstream(in, file, line, pos);
703
704 skipTo(in, file, line, pos, ":", WHITESPACE);
705 // skip the :
706
707 ConstElementPtr value =
708 Element::fromJSON(in, file, line, pos, level - 1);
709 map->set(key, value);
710
711 c = skipTo(in, file, line, pos, ",}", WHITESPACE);
712 }
713 }
714 return (map);
715}
716} // end anonymous namespace
717
718std::string
720 switch (type) {
721 case Element::integer:
722 return (std::string("integer"));
723 case Element::bigint:
724 return (std::string("bigint"));
725 case Element::real:
726 return (std::string("real"));
727 case Element::boolean:
728 return (std::string("boolean"));
729 case Element::string:
730 return (std::string("string"));
731 case Element::list:
732 return (std::string("list"));
733 case Element::map:
734 return (std::string("map"));
735 case Element::null:
736 return (std::string("null"));
737 case Element::any:
738 return (std::string("any"));
739 default:
740 return (std::string("unknown"));
741 }
742}
743
745Element::nameToType(const std::string& type_name) {
746 if (type_name == "integer") {
747 return (Element::integer);
748 } else if (type_name == "bigint") {
749 return (Element::bigint);
750 } else if (type_name == "real") {
751 return (Element::real);
752 } else if (type_name == "boolean") {
753 return (Element::boolean);
754 } else if (type_name == "string") {
755 return (Element::string);
756 } else if (type_name == "list") {
757 return (Element::list);
758 } else if (type_name == "map") {
759 return (Element::map);
760 } else if (type_name == "named_set") {
761 return (Element::map);
762 } else if (type_name == "null") {
763 return (Element::null);
764 } else if (type_name == "any") {
765 return (Element::any);
766 } else {
767 isc_throw(TypeError, type_name + " is not a valid type name");
768 }
769}
770
772Element::fromJSON(std::istream& in, bool preproc) {
773
774 int line = 1, pos = 1;
775 stringstream filtered;
776 if (preproc) {
777 preprocess(in, filtered);
778 }
779
780 ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
781
782 return (value);
783}
784
786Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) {
787 int line = 1, pos = 1;
788 stringstream filtered;
789 if (preproc) {
790 preprocess(in, filtered);
791 }
792 return (fromJSON(preproc ? filtered : in, file_name, line, pos));
793}
794
796Element::fromJSON(std::istream& in, const std::string& file, int& line,
797 int& pos, unsigned level) {
798 if (level == 0) {
799 isc_throw(JSONError, "fromJSON elements nested too deeply");
800 }
801 int c = 0;
802 ElementPtr element;
803 bool el_read = false;
804 skipChars(in, WHITESPACE, line, pos);
805 while (c != EOF && !el_read) {
806 c = in.get();
807 pos++;
808 switch(c) {
809 case '1':
810 case '2':
811 case '3':
812 case '4':
813 case '5':
814 case '6':
815 case '7':
816 case '8':
817 case '9':
818 case '0':
819 case '-':
820 case '+':
821 case '.':
822 in.putback(c);
823 --pos;
824 element = fromStringstreamNumber(in, file, line, pos);
825 el_read = true;
826 break;
827 case 't':
828 case 'f':
829 in.putback(c);
830 --pos;
831 element = fromStringstreamBool(in, file, line, pos);
832 el_read = true;
833 break;
834 case 'n':
835 in.putback(c);
836 --pos;
837 element = fromStringstreamNull(in, file, line, pos);
838 el_read = true;
839 break;
840 case '"':
841 in.putback('"');
842 --pos;
843 element = fromStringstreamString(in, file, line, pos);
844 el_read = true;
845 break;
846 case '[':
847 element = fromStringstreamList(in, file, line, pos, level);
848 el_read = true;
849 break;
850 case '{':
851 element = fromStringstreamMap(in, file, line, pos, level);
852 el_read = true;
853 break;
854 case EOF:
855 break;
856 default:
857 throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
858 break;
859 }
860 }
861 if (el_read) {
862 return (element);
863 } else {
864 isc_throw(JSONError, "nothing read");
865 }
866}
867
869Element::fromJSON(const std::string& in, bool preproc) {
870 std::stringstream ss;
871 ss << in;
872
873 int line = 1, pos = 1;
874 stringstream filtered;
875 if (preproc) {
876 preprocess(ss, filtered);
877 }
878 ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
879 skipChars(ss, WHITESPACE, line, pos);
880 // ss must now be at end
881 if (ss.peek() != EOF) {
882 throwJSONError("Extra data", "<string>", line, pos);
883 }
884 return result;
885}
886
888Element::fromJSONFile(const std::string& file_name, bool preproc) {
889 // zero out the errno to be safe
890 errno = 0;
891
892 std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
893 if (!infile.is_open()) {
894 const char* error = strerror(errno);
895 isc_throw(InvalidOperation, "failed to read file '" << file_name
896 << "': " << error);
897 }
898
899 return (fromJSON(infile, file_name, preproc));
900}
901
902// to JSON format
903
904void
905IntElement::toJSON(std::ostream& ss, unsigned) const {
906 ss << intValue();
907}
908
909void
910BigIntElement::toJSON(std::ostream& ss, unsigned) const {
911 ss << bigIntValue();
912}
913
914void
915DoubleElement::toJSON(std::ostream& ss, unsigned) const {
916 // The default output for doubles nicely drops off trailing
917 // zeros, however this produces strings without decimal points
918 // for whole number values. When reparsed this will create
919 // IntElements not DoubleElements. Rather than used a fixed
920 // precision, we'll just tack on an ".0" when the decimal point
921 // is missing.
922 ostringstream val_ss;
923 val_ss << doubleValue();
924 ss << val_ss.str();
925 if (val_ss.str().find_first_of('.') == string::npos) {
926 ss << ".0";
927 }
928}
929
930void
931BoolElement::toJSON(std::ostream& ss, unsigned) const {
932 if (boolValue()) {
933 ss << "true";
934 } else {
935 ss << "false";
936 }
937}
938
939void
940NullElement::toJSON(std::ostream& ss, unsigned) const {
941 ss << "null";
942}
943
944void
945StringElement::toJSON(std::ostream& ss, unsigned) const {
946 ss << "\"";
947 const std::string& str = stringValue();
948 for (size_t i = 0; i < str.size(); ++i) {
949 const signed char c = str[i];
950 // Escape characters as defined in JSON spec
951 // Note that we do not escape forward slash; this
952 // is allowed, but not mandatory.
953 switch (c) {
954 case '"':
955 ss << '\\' << c;
956 break;
957 case '\\':
958 ss << '\\' << c;
959 break;
960 case '\b':
961 ss << '\\' << 'b';
962 break;
963 case '\f':
964 ss << '\\' << 'f';
965 break;
966 case '\n':
967 ss << '\\' << 'n';
968 break;
969 case '\r':
970 ss << '\\' << 'r';
971 break;
972 case '\t':
973 ss << '\\' << 't';
974 break;
975 default:
976 if (c < 0x20 || c == 0x7f) {
977 std::ostringstream esc;
978 esc << "\\u"
979 << hex
980 << setw(4)
981 << setfill('0')
982 << (static_cast<unsigned>(c) & 0xff);
983 ss << esc.str();
984 } else {
985 ss << c;
986 }
987 }
988 }
989 ss << "\"";
990}
991
992void
993ListElement::toJSON(std::ostream& ss, unsigned level) const {
994 if (level == 0) {
995 isc_throw(BadValue, "toJSON got infinite recursion: "
996 "arguments include cycles");
997 }
998 ss << "[ ";
999
1000 const std::vector<ElementPtr>& v = listValue();
1001 bool first = true;
1002 for (auto const& it : v) {
1003 if (!first) {
1004 ss << ", ";
1005 } else {
1006 first = false;
1007 }
1008 it->toJSON(ss, level - 1);
1009 }
1010 ss << " ]";
1011}
1012
1013void
1014MapElement::toJSON(std::ostream& ss, unsigned level) const {
1015 if (level == 0) {
1016 isc_throw(BadValue, "toJSON got infinite recursion: "
1017 "arguments include cycles");
1018 }
1019 ss << "{ ";
1020
1021 bool first = true;
1022 for (auto const& it : m) {
1023 if (!first) {
1024 ss << ", ";
1025 } else {
1026 first = false;
1027 }
1028 ss << "\"" << it.first << "\": ";
1029 if (it.second) {
1030 it.second->toJSON(ss, level - 1);
1031 } else {
1032 ss << "None";
1033 }
1034 }
1035 ss << " }";
1036}
1037
1038// throws when one of the types in the path (except the one
1039// we're looking for) is not a MapElement
1040// returns 0 if it could simply not be found
1041// should that also be an exception?
1043MapElement::find(const std::string& id) const {
1044 const size_t sep = id.find('/');
1045 if (sep == std::string::npos) {
1046 return (get(id));
1047 } else {
1048 ConstElementPtr ce = get(id.substr(0, sep));
1049 if (ce) {
1050 // ignore trailing slash
1051 if (sep + 1 != id.size()) {
1052 return (ce->find(id.substr(sep + 1)));
1053 } else {
1054 return (ce);
1055 }
1056 } else {
1057 return (ElementPtr());
1058 }
1059 }
1060}
1061
1063Element::fromWire(const std::string& s) {
1064 std::stringstream ss;
1065 ss << s;
1066 int line = 0, pos = 0;
1067 return (fromJSON(ss, "<wire>", line, pos));
1068}
1069
1071Element::fromWire(std::stringstream& in, int) {
1072 //
1073 // Check protocol version
1074 //
1075 //for (int i = 0 ; i < 4 ; ++i) {
1076 // const unsigned char version_byte = get_byte(in);
1077 // if (PROTOCOL_VERSION[i] != version_byte) {
1078 // throw DecodeError("Protocol version incorrect");
1079 // }
1080 //}
1081 //length -= 4;
1082 int line = 0, pos = 0;
1083 return (fromJSON(in, "<wire>", line, pos));
1084}
1085
1086void
1087MapElement::set(const std::string& key, ConstElementPtr value) {
1088 m[key] = value;
1089}
1090
1091bool
1092MapElement::find(const std::string& id, ConstElementPtr& t) const {
1093 try {
1094 ConstElementPtr p = find(id);
1095 if (p) {
1096 t = p;
1097 return (true);
1098 }
1099 } catch (const TypeError&) {
1100 // ignore
1101 }
1102 return (false);
1103}
1104
1105bool
1106IntElement::equals(const Element& other, unsigned) const {
1107 // Let's not be very picky with constraining the integer types to be the
1108 // same. Equality is sometimes checked from high-up in the Element hierarchy.
1109 // That is a context which, most of the time, does not have information on
1110 // the type of integers stored on Elements lower in the hierarchy. So it
1111 // would be difficult to differentiate between the integer types.
1112 return (other.getType() == Element::integer && i == other.intValue()) ||
1113 (other.getType() == Element::bigint && i == other.bigIntValue());
1114}
1115
1116bool
1117BigIntElement::equals(const Element& other, unsigned) const {
1118 // Let's not be very picky with constraining the integer types to be the
1119 // same. Equality is sometimes checked from high-up in the Element hierarchy.
1120 // That is a context which, most of the time, does not have information on
1121 // the type of integers stored on Elements lower in the hierarchy. So it
1122 // would be difficult to differentiate between the integer types.
1123 return (other.getType() == Element::bigint && i_ == other.bigIntValue()) ||
1124 (other.getType() == Element::integer && i_ == other.intValue());
1125}
1126
1127bool
1128DoubleElement::equals(const Element& other, unsigned) const {
1129 return (other.getType() == Element::real) &&
1130 (fabs(d - other.doubleValue()) < 1e-14);
1131}
1132
1133bool
1134BoolElement::equals(const Element& other, unsigned) const {
1135 return (other.getType() == Element::boolean) &&
1136 (b == other.boolValue());
1137}
1138
1139bool
1140NullElement::equals(const Element& other, unsigned) const {
1141 return (other.getType() == Element::null);
1142}
1143
1144bool
1145StringElement::equals(const Element& other, unsigned) const {
1146 return (other.getType() == Element::string) &&
1147 (s == other.stringValue());
1148}
1149
1150bool
1151ListElement::equals(const Element& other, unsigned level) const {
1152 if (level == 0) {
1153 isc_throw(BadValue, "equals got infinite recursion: "
1154 "arguments include cycles");
1155 }
1156 if (other.getType() == Element::list) {
1157 const size_t s = size();
1158 if (s != other.size()) {
1159 return (false);
1160 }
1161 for (size_t i = 0; i < s; ++i) {
1162 if (!get(i)->equals(*other.get(i), level - 1)) {
1163 return (false);
1164 }
1165 }
1166 return (true);
1167 } else {
1168 return (false);
1169 }
1170}
1171
1172void
1173ListElement::sort(std::string const& index /* = std::string() */) {
1174 if (l.empty()) {
1175 return;
1176 }
1177
1178 int const t(l.at(0)->getType());
1179 std::function<bool(ElementPtr, ElementPtr)> comparator;
1180 if (t == map) {
1181 if (index.empty()) {
1182 isc_throw(BadValue, "index required when sorting maps");
1183 }
1184 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1185 ConstElementPtr const& ai(a->get(index));
1186 ConstElementPtr const& bi(b->get(index));
1187 if (ai && bi) {
1188 return *ai < *bi;
1189 }
1190 return true;
1191 };
1192 } else if (t == list) {
1193 // Nested lists. Not supported.
1194 return;
1195 } else {
1196 // Assume scalars.
1197 if (!index.empty()) {
1198 isc_throw(BadValue, "index given when sorting scalars?");
1199 }
1200 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1201 return *a < *b;
1202 };
1203 }
1204
1205 std::sort(l.begin(), l.end(), comparator);
1206}
1207
1208bool
1209MapElement::equals(const Element& other, unsigned level) const {
1210 if (level == 0) {
1211 isc_throw(BadValue, "equals got infinite recursion: "
1212 "arguments include cycles");
1213 }
1214 if (other.getType() == Element::map) {
1215 if (size() != other.size()) {
1216 return (false);
1217 }
1218 for (auto const& kv : mapValue()) {
1219 auto key = kv.first;
1220 if (other.contains(key)) {
1221 if (!get(key)->equals(*other.get(key), level - 1)) {
1222 return (false);
1223 }
1224 } else {
1225 return (false);
1226 }
1227 }
1228 return (true);
1229 } else {
1230 return (false);
1231 }
1232}
1233
1234bool
1236 return (!p);
1237}
1238
1239void
1241 if (!b) {
1242 return;
1243 }
1244 if (a->getType() != Element::map || b->getType() != Element::map) {
1245 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1246 }
1247
1248 // As maps do not allow entries with multiple keys, we can either iterate
1249 // over a checking for identical entries in b or vice-versa. As elements
1250 // are removed from a if a match is found, we choose to iterate over b to
1251 // avoid problems with element removal affecting the iterator.
1252 for (auto const& kv : b->mapValue()) {
1253 auto key = kv.first;
1254 if (a->contains(key)) {
1255 if (a->get(key)->equals(*b->get(key))) {
1256 a->remove(key);
1257 }
1258 }
1259 }
1260}
1261
1264 ElementPtr result = Element::createMap();
1265
1266 if (!b) {
1267 return (result);
1268 }
1269
1270 if (a->getType() != Element::map || b->getType() != Element::map) {
1271 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1272 }
1273
1274 for (auto const& kv : a->mapValue()) {
1275 auto key = kv.first;
1276 if (!b->contains(key) ||
1277 !a->get(key)->equals(*b->get(key))) {
1278 result->set(key, kv.second);
1279 }
1280 }
1281
1282 return (result);
1283}
1284
1285void
1287 if (element->getType() != Element::map ||
1288 other->getType() != Element::map) {
1289 isc_throw(TypeError, "merge arguments not MapElements");
1290 }
1291
1292 for (auto const& kv : other->mapValue()) {
1293 auto key = kv.first;
1294 auto value = kv.second;
1295 if (value && value->getType() != Element::null) {
1296 element->set(key, value);
1297 } else if (element->contains(key)) {
1298 element->remove(key);
1299 }
1300 }
1301}
1302
1303void
1305 HierarchyDescriptor& hierarchy, std::string key, size_t idx,
1306 unsigned level) {
1307 if (level == 0) {
1308 isc_throw(BadValue, "mergeDiffAdd got infinite recursion: "
1309 "arguments include cycles");
1310 }
1311 if (element->getType() != other->getType()) {
1312 isc_throw(TypeError, "mergeDiffAdd arguments not same type");
1313 }
1314
1315 if (element->getType() == Element::list) {
1316 // Store new elements in a separate container so we don't overwrite
1317 // options as we add them (if there are duplicates).
1318 ElementPtr new_elements = Element::createList();
1319 for (auto const& right : other->listValue()) {
1320 // Check if we have any description of the key in the configuration
1321 // hierarchy.
1322 auto f = hierarchy[idx].find(key);
1323 if (f != hierarchy[idx].end()) {
1324 bool found = false;
1325 ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
1326 for (auto const& left : element->listValue()) {
1327 ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
1328 // Check if the elements refer to the same configuration
1329 // entity.
1330 if (f->second.match_(mutable_left, mutable_right)) {
1331 found = true;
1332 mergeDiffAdd(mutable_left, mutable_right, hierarchy,
1333 key, idx, level - 1);
1334 }
1335 }
1336 if (!found) {
1337 new_elements->add(right);
1338 }
1339 } else {
1340 new_elements->add(right);
1341 }
1342 }
1343 // Finally add the new elements.
1344 for (auto const& right : new_elements->listValue()) {
1345 element->add(right);
1346 }
1347 return;
1348 }
1349
1350 if (element->getType() == Element::map) {
1351 for (auto const& kv : other->mapValue()) {
1352 auto current_key = kv.first;
1353 auto value = boost::const_pointer_cast<Element>(kv.second);
1354 if (value && value->getType() != Element::null) {
1355 if (element->contains(current_key) &&
1356 (value->getType() == Element::map ||
1357 value->getType() == Element::list)) {
1358 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1359 mergeDiffAdd(mutable_element, value, hierarchy,
1360 current_key, idx + 1, level - 1);
1361 } else {
1362 element->set(current_key, value);
1363 }
1364 }
1365 }
1366 return;
1367 }
1368 element = other;
1369}
1370
1371void
1373 HierarchyDescriptor& hierarchy, std::string key, size_t idx,
1374 unsigned level) {
1375 if (level == 0) {
1376 isc_throw(BadValue, "mergeDiffDel got infinite recursion: "
1377 "arguments include cycles");
1378 }
1379 if (element->getType() != other->getType()) {
1380 isc_throw(TypeError, "mergeDiffDel arguments not same type");
1381 }
1382
1383 if (element->getType() == Element::list) {
1384 for (auto const& value : other->listValue()) {
1385 ElementPtr mutable_right = boost::const_pointer_cast<Element>(value);
1386 for (uint32_t iter = 0; iter < element->listValue().size();) {
1387 bool removed = false;
1388 // Check if we have any description of the key in the
1389 // configuration hierarchy.
1390 auto f = hierarchy[idx].find(key);
1391 if (f != hierarchy[idx].end()) {
1392 ElementPtr mutable_left = boost::const_pointer_cast<Element>(element->listValue().at(iter));
1393 // Check if the elements refer to the same configuration
1394 // entity.
1395 if (f->second.match_(mutable_left, mutable_right)) {
1396 // Check if the user supplied data only contains
1397 // identification information, so the intent is to
1398 // delete the element, not just element data.
1399 if (f->second.no_data_(mutable_right)) {
1400 element->remove(iter);
1401 removed = true;
1402 } else {
1403 mergeDiffDel(mutable_left, mutable_right,
1404 hierarchy, key, idx, level - 1);
1405 if (mutable_left->empty()) {
1406 element->remove(iter);
1407 removed = true;
1408 }
1409 }
1410 }
1411 } else if (element->listValue().at(iter)->equals(*value)) {
1412 element->remove(iter);
1413 removed = true;
1414 }
1415 if (!removed) {
1416 ++iter;
1417 }
1418 }
1419 }
1420 return;
1421 }
1422
1423 if (element->getType() == Element::map) {
1424 // If the resulting element still contains data, we need to restore the
1425 // key parameters, so we store them here.
1426 ElementPtr new_elements = Element::createMap();
1427 for (auto const& kv : other->mapValue()) {
1428 auto current_key = kv.first;
1429 auto value = boost::const_pointer_cast<Element>(kv.second);
1430 if (value && value->getType() != Element::null) {
1431 if (element->contains(current_key)) {
1432 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1433 if (mutable_element->getType() == Element::map ||
1434 mutable_element->getType() == Element::list) {
1435 mergeDiffDel(mutable_element, value, hierarchy,
1436 current_key, idx + 1, level - 1);
1437 if (mutable_element->empty()) {
1438 element->remove(current_key);
1439 }
1440 } else {
1441 // Check if we have any description of the key in the
1442 // configuration hierarchy.
1443 auto f = hierarchy[idx].find(key);
1444 if (f != hierarchy[idx].end()) {
1445 // Check if the key is used for element
1446 // identification.
1447 if (f->second.is_key_(current_key)) {
1448 // Store the key parameter.
1449 new_elements->set(current_key, mutable_element);
1450 }
1451 }
1452 element->remove(current_key);
1453 }
1454 }
1455 }
1456 }
1457 // If the element still contains data, restore the key elements.
1458 if (element->size()) {
1459 for (auto const& kv : new_elements->mapValue()) {
1460 element->set(kv.first, kv.second);
1461 }
1462 }
1463 return;
1464 }
1465 element = ElementPtr(new NullElement());
1466}
1467
1468void
1469extend(const std::string& container, const std::string& extension,
1470 ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy,
1471 std::string key, size_t idx, bool alter, unsigned level) {
1472
1473 if (level == 0) {
1474 isc_throw(BadValue, "extend got infinite recursion: "
1475 "arguments include cycles");
1476 }
1477 if (element->getType() != other->getType()) {
1478 isc_throw(TypeError, "extend arguments not same type");
1479 }
1480
1481 if (element->getType() == Element::list) {
1482 for (auto const& right : other->listValue()) {
1483 // Check if we have any description of the key in the configuration
1484 // hierarchy.
1485 auto f = hierarchy[idx].find(key);
1486 if (f != hierarchy[idx].end()) {
1487 ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
1488 for (auto const& left : element->listValue()) {
1489 ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
1490 if (container == key) {
1491 alter = true;
1492 }
1493 if (f->second.match_(mutable_left, mutable_right)) {
1494 extend(container, extension, mutable_left, mutable_right,
1495 hierarchy, key, idx, alter, level - 1);
1496 }
1497 }
1498 }
1499 }
1500 return;
1501 }
1502
1503 if (element->getType() == Element::map) {
1504 for (auto const& kv : other->mapValue()) {
1505 auto current_key = kv.first;
1506 auto value = boost::const_pointer_cast<Element>(kv.second);
1507 if (value && value->getType() != Element::null) {
1508 if (element->contains(current_key) &&
1509 (value->getType() == Element::map ||
1510 value->getType() == Element::list)) {
1511 ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
1512 if (container == key) {
1513 alter = true;
1514 }
1515 extend(container, extension, mutable_element, value,
1516 hierarchy, current_key, idx + 1, alter, level - 1);
1517 } else if (alter && current_key == extension) {
1518 element->set(current_key, value);
1519 }
1520 }
1521 }
1522 return;
1523 }
1524}
1525
1527copy(ConstElementPtr from, unsigned level) {
1528 if (!from) {
1529 isc_throw(BadValue, "copy got a null pointer");
1530 }
1531
1532 auto pos = from->getPosition();
1533 int from_type = from->getType();
1534 if (from_type == Element::integer) {
1535 return (ElementPtr(new IntElement(from->intValue(), pos)));
1536 } else if (from_type == Element::real) {
1537 return (ElementPtr(new DoubleElement(from->doubleValue(), pos)));
1538 } else if (from_type == Element::boolean) {
1539 return (ElementPtr(new BoolElement(from->boolValue(), pos)));
1540 } else if (from_type == Element::null) {
1541 return (ElementPtr(new NullElement()));
1542 } else if (from_type == Element::string) {
1543 return (ElementPtr(new StringElement(from->stringValue(), pos)));
1544 } else if (from_type == Element::list) {
1545 ElementPtr result = ElementPtr(new ListElement(pos));
1546 for (auto const& elem : from->listValue()) {
1547 if (level == 0) {
1548 result->add(elem);
1549 } else {
1550 result->add(copy(elem, level - 1));
1551 }
1552 }
1553 return (result);
1554 } else if (from_type == Element::map) {
1555 ElementPtr result = ElementPtr(new MapElement(pos));
1556 for (auto const& kv : from->mapValue()) {
1557 auto key = kv.first;
1558 auto value = kv.second;
1559 if (level == 0) {
1560 result->set(key, value);
1561 } else {
1562 result->set(key, copy(value, level - 1));
1563 }
1564 }
1565 return (result);
1566 } else {
1567 isc_throw(BadValue, "copy got an element of type: " << from_type);
1568 }
1569}
1570
1571namespace {
1572
1573// Helper function which blocks infinite recursion
1574bool
1575isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) {
1576 // check looping forever on cycles
1577 if (!level) {
1578 isc_throw(BadValue, "isEquivalent got infinite recursion: "
1579 "arguments include cycles");
1580 }
1581 if (!a || !b) {
1582 isc_throw(BadValue, "isEquivalent got a null pointer");
1583 }
1584 // check types
1585 if (a->getType() != b->getType()) {
1586 return (false);
1587 }
1588 if (a->getType() == Element::list) {
1589 // check empty
1590 if (a->empty()) {
1591 return (b->empty());
1592 }
1593 // check size
1594 if (a->size() != b->size()) {
1595 return (false);
1596 }
1597
1598 // copy b into a list
1599 const size_t s = a->size();
1600 std::list<ConstElementPtr> l;
1601 for (size_t i = 0; i < s; ++i) {
1602 l.push_back(b->get(i));
1603 }
1604
1605 // iterate on a
1606 for (size_t i = 0; i < s; ++i) {
1607 ConstElementPtr item = a->get(i);
1608 // lookup this item in the list
1609 bool found = false;
1610 for (auto it = l.begin(); it != l.end(); ++it) {
1611 // if found in the list remove it
1612 if (isEquivalent0(item, *it, level - 1)) {
1613 found = true;
1614 l.erase(it);
1615 break;
1616 }
1617 }
1618 // if not found argument differs
1619 if (!found) {
1620 return (false);
1621 }
1622 }
1623
1624 // sanity check: the list must be empty
1625 if (!l.empty()) {
1626 isc_throw(Unexpected, "isEquivalent internal error");
1627 }
1628 return (true);
1629 } else if (a->getType() == Element::map) {
1630 // check sizes
1631 if (a->size() != b->size()) {
1632 return (false);
1633 }
1634 // iterate on the first map
1635 for (auto const& kv : a->mapValue()) {
1636 // get the b value for the given keyword and recurse
1637 ConstElementPtr item = b->get(kv.first);
1638 if (!item || !isEquivalent0(kv.second, item, level - 1)) {
1639 return (false);
1640 }
1641 }
1642 return (true);
1643 } else {
1644 return (a->equals(*b));
1645 }
1646}
1647
1648} // end anonymous namespace
1649
1650bool
1652 return (isEquivalent0(a, b, 100));
1653}
1654
1655namespace {
1656
1657void
1658prettyPrint0(ConstElementPtr element, std::ostream& out,
1659 unsigned indent, unsigned step, unsigned level) {
1660 if (level == 0) {
1661 isc_throw(BadValue, "prettyPrint got infinite recursion: "
1662 "arguments include cycles");
1663 }
1664 if (!element) {
1665 isc_throw(BadValue, "prettyPrint got a null pointer");
1666 }
1667 if (element->getType() == Element::list) {
1668 // empty list case
1669 if (element->empty()) {
1670 out << "[ ]";
1671 return;
1672 }
1673
1674 // complex ? multiline : oneline
1675 if (!element->get(0)) {
1676 isc_throw(BadValue, "prettyPrint got a null pointer");
1677 }
1678 int first_type = element->get(0)->getType();
1679 bool complex = false;
1680 if ((first_type == Element::list) || (first_type == Element::map)) {
1681 complex = true;
1682 }
1683 std::string separator = complex ? ",\n" : ", ";
1684
1685 // open the list
1686 out << "[" << (complex ? "\n" : " ");
1687
1688 // iterate on items
1689 auto const& l = element->listValue();
1690 bool first = true;
1691 for (auto const& it : l) {
1692 // add the separator if not the first item
1693 if (!first) {
1694 out << separator;
1695 } else {
1696 first = false;
1697 }
1698 // add indentation
1699 if (complex) {
1700 out << std::string(indent + step, ' ');
1701 }
1702 // recursive call
1703 prettyPrint0(it, out, indent + step, step, level - 1);
1704 }
1705
1706 // close the list
1707 if (complex) {
1708 out << "\n" << std::string(indent, ' ');
1709 } else {
1710 out << " ";
1711 }
1712 out << "]";
1713 } else if (element->getType() == Element::map) {
1714 // empty map case
1715 if (element->size() == 0) {
1716 out << "{ }";
1717 return;
1718 }
1719
1720 // open the map
1721 out << "{\n";
1722
1723 // iterate on keyword: value
1724 auto const& m = element->mapValue();
1725 bool first = true;
1726 for (auto const& it : m) {
1727 // add the separator if not the first item
1728 if (first) {
1729 first = false;
1730 } else {
1731 out << ",\n";
1732 }
1733 // add indentation
1734 out << std::string(indent + step, ' ');
1735 // add keyword:
1736 out << "\"" << it.first << "\": ";
1737 // recursive call
1738 prettyPrint0(it.second, out, indent + step, step, level - 1);
1739 }
1740
1741 // close the map
1742 out << "\n" << std::string(indent, ' ') << "}";
1743 } else {
1744 // not a list or a map
1745 element->toJSON(out);
1746 }
1747}
1748
1749} // end anonymous namespace
1750
1751void
1752prettyPrint(ConstElementPtr element, std::ostream& out,
1753 unsigned indent, unsigned step) {
1754 prettyPrint0(element, out, indent, step, Element::MAX_NESTING_LEVEL);
1755}
1756
1757std::string
1758prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
1759 std::stringstream ss;
1760 prettyPrint(element, ss, indent, step);
1761 return (ss.str());
1762}
1763
1764void Element::preprocess(std::istream& in, std::stringstream& out) {
1765
1766 std::string line;
1767
1768 while (std::getline(in, line)) {
1769 // If this is a comments line, replace it with empty line
1770 // (so the line numbers will still match
1771 if (!line.empty() && line[0] == '#') {
1772 line = "";
1773 }
1774
1775 // getline() removes end line characters. Unfortunately, we need
1776 // it for getting the line numbers right (in case we report an
1777 // error.
1778 out << line;
1779 out << "\n";
1780 }
1781}
1782
1783namespace {
1784
1785// Type of arcs.
1786typedef std::set<ConstElementPtr> Arc;
1787
1788// Helper function walking on the supposed tree.
1789bool
1790IsCircular0(ConstElementPtr element, Arc arc) {
1791 // Sanity check.
1792 if (!element) {
1793 return (false);
1794 }
1795 auto type = element->getType();
1796 // Container?
1797 if ((type != Element::list) && (type != Element::map)) {
1798 return (false);
1799 }
1800 // Empty? A cycle requires at least one element.
1801 if (element->empty()) {
1802 return (false);
1803 }
1804 // In the arc?
1805 if (arc.count(element) > 0) {
1806 return (true);
1807 }
1808 // This requires to work on a copy of the arc but it should be small.
1809 arc.insert(element);
1810 if (type == Element::list) {
1811 for (auto const& it : element->listValue()) {
1812 if (IsCircular0(it, arc)) {
1813 return (true);
1814 }
1815 }
1816 return (false);
1817 }
1818 // The argument is a map.
1819 for (auto const& it : element->mapValue()) {
1820 if (IsCircular0(it.second, arc)) {
1821 return (true);
1822 }
1823 }
1824 return (false);
1825}
1826
1827} // end anonymous namespace
1828
1829bool
1831 return (IsCircular0(element, Arc()));
1832}
1833
1834unsigned
1835getNestDepth(ConstElementPtr element, unsigned max_depth) {
1836 if (max_depth == 0U) {
1837 return (0U);
1838 }
1839 if (!element) {
1840 return (0U);
1841 }
1842 unsigned ret = 1U;
1843 if (element->getType() == Element::list) {
1844 for (auto const& i : element->listValue()) {
1845 unsigned sub = getNestDepth(i, max_depth - 1);
1846 if (sub == max_depth - 1) {
1847 return (max_depth);
1848 }
1849 if (sub + 1 > ret) {
1850 ret = sub + 1;
1851 }
1852 }
1853 } else if (element->getType() == Element::map) {
1854 for (auto const& i : element->mapValue()) {
1855 unsigned sub = getNestDepth(i.second, max_depth - 1);
1856 if (sub == max_depth - 1) {
1857 return (max_depth);
1858 }
1859 if (sub + 1 > ret) {
1860 ret = sub + 1;
1861 }
1862 }
1863
1864 }
1865 return (ret);
1866}
1867
1868} // end of isc::data namespace
1869} // end of isc namespace
@ map
Definition data.h:160
@ list
Definition data.h:159
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
Wrapper over int128_t.
Definition data.h:793
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const override
Converts the Element to JSON format and appends it to the given stringstream.
Definition data.cc:910
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const override
Checks whether the other Element is equal.
Definition data.cc:1117
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1134
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:931
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:915
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1128
The Element class represents a piece of data, used by the command channel and configuration parts.
Definition data.h:73
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:299
virtual bool getValue(int64_t &t) const
Get the integer value.
Definition data.cc:123
static std::string typeToName(Element::types type)
Returns the name of the given type as a string.
Definition data.cc:719
virtual int64_t intValue() const
Return the integer value.
Definition data.h:275
static constexpr unsigned MAX_NESTING_LEVEL
Maximum nesting level of Element objects.
Definition data.h:86
std::string str() const
Returns a string representing the Element and all its child elements.
Definition data.cc:104
virtual std::string stringValue() const
Return the string value.
Definition data.h:295
std::string toWire() const
Returns the wireformat for the Element and all its child elements.
Definition data.cc:111
types
The types that an Element can hold.
Definition data.h:152
virtual void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const =0
Converts the Element to JSON format and appends it to the given output stream.
static ElementPtr fromWire(std::stringstream &in, int length)
These function parse the wireformat at the given stringstream (of the given length).
Definition data.cc:1071
virtual bool setValue(const long long int v)
Set the integer value.
Definition data.cc:153
static ElementPtr fromJSONFile(const std::string &file_name, bool preproc=false)
Reads contents of specified file and interprets it as JSON.
Definition data.cc:888
virtual bool empty() const
Return true if there are no elements in the list.
Definition data.cc:218
virtual void remove(const int i)
Removes the element at the given position.
Definition data.cc:208
virtual bool contains(const std::string &name) const
Checks if there is data at the given key.
Definition data.cc:238
virtual ConstElementPtr find(const std::string &identifier) const
Recursively finds any data at the given identifier.
Definition data.cc:243
virtual size_t size() const
Returns the number of elements in the list.
Definition data.cc:213
Element(types t, const Position &pos=ZERO_POSITION())
Constructor.
Definition data.h:181
virtual const std::map< std::string, ConstElementPtr > & mapValue() const
Return the map value.
Definition data.h:306
virtual void add(ElementPtr element)
Adds an ElementPtr to the list.
Definition data.cc:203
virtual const std::vector< ElementPtr > & listValue() const
Return the list value.
Definition data.h:300
virtual bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const =0
Test equality.
static ElementPtr fromJSON(const std::string &in, bool preproc=false)
These functions will parse the given string (JSON) representation of a compound element.
Definition data.cc:869
virtual ConstElementPtr get(const int i) const
Returns the ElementPtr at the given index.
Definition data.cc:188
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:354
static Element::types nameToType(const std::string &type_name)
Converts the string to the corresponding type Throws a TypeError if the name is unknown.
Definition data.cc:745
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
virtual void set(const size_t i, ElementPtr element)
Sets the ElementPtr at the given index.
Definition data.cc:198
virtual double doubleValue() const
Return the double value.
Definition data.h:285
virtual isc::util::int128_t bigIntValue() const
Return the big integer value.
Definition data.h:280
types getType() const
Definition data.h:191
virtual bool boolValue() const
Return the boolean value.
Definition data.h:290
static void preprocess(std::istream &in, std::stringstream &out)
input text preprocessor.
Definition data.cc:1764
virtual ElementPtr getNonConst(const int i) const
returns element as non-const pointer.
Definition data.cc:193
void removeEmptyContainersRecursively(unsigned level=MAX_NESTING_LEVEL)
Remove all empty maps and lists from this Element and its descendants.
Definition data.cc:57
Notes: IntElement type is changed to int64_t.
Definition data.h:776
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:905
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1106
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition data.h:50
void sort(std::string const &index=std::string())
Sorts the elements inside the list.
Definition data.cc:1173
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:993
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1151
ConstElementPtr find(const std::string &id) const override
Recursively finds any data at the given identifier.
Definition data.cc:1043
void set(const std::string &key, ConstElementPtr value) override
Sets the ElementPtr at the given key.
Definition data.cc:1087
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const override
Test equality.
Definition data.cc:1209
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const override
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:1014
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1140
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:940
bool equals(const Element &other, unsigned level=MAX_NESTING_LEVEL) const
Test equality.
Definition data.cc:1145
void toJSON(std::ostream &ss, unsigned level=MAX_NESTING_LEVEL) const
Converts the Element to JSON format and appends it to the given output stream.
Definition data.cc:945
A standard Data module exception that is thrown if a function is called for an Element that has a wro...
Definition data.h:37
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
#define throwTypeError(error)
Add the position to a TypeError message should be used in place of isc_throw(TypeError,...
Definition data.h:234
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
bool operator==(const Element &a, const Element &b)
Test equality.
Definition data.cc:268
void extend(const std::string &container, const std::string &extension, ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, bool alter, unsigned level)
Extends data by adding the specified 'extension' elements from 'other' inside the 'container' element...
Definition data.cc:1469
void removeIdentical(ElementPtr a, ConstElementPtr b)
Remove all values from the first ElementPtr that are equal in the second.
Definition data.cc:1240
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element. (on the first level).
Definition data.cc:1286
bool isEquivalent(ConstElementPtr a, ConstElementPtr b)
Compares the data with other using unordered lists.
Definition data.cc:1651
bool operator<(Element const &a, Element const &b)
Test less than.
Definition data.cc:277
bool IsCircular(ConstElementPtr element)
Check if the data is circular.
Definition data.cc:1830
void prettyPrint(ConstElementPtr element, std::ostream &out, unsigned indent, unsigned step)
Pretty prints the data into stream.
Definition data.cc:1752
ElementPtr copy(ConstElementPtr from, unsigned level)
Copy the data up to a nesting level.
Definition data.cc:1527
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
void mergeDiffAdd(ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, unsigned level)
Merges the diff data by adding the missing elements from 'other' to 'element' (recursively).
Definition data.cc:1304
unsigned getNestDepth(ConstElementPtr element, unsigned max_depth)
Compute the nesting depth.
Definition data.cc:1835
bool isNull(ConstElementPtr p)
Checks whether the given ElementPtr is a null pointer.
Definition data.cc:1235
std::ostream & operator<<(std::ostream &out, const Element::Position &pos)
Insert Element::Position as a string into stream.
Definition data.cc:51
void mergeDiffDel(ElementPtr &element, ElementPtr &other, HierarchyDescriptor &hierarchy, std::string key, size_t idx, unsigned level)
Merges the diff data by removing the data present in 'other' from 'element' (recursively).
Definition data.cc:1372
bool operator!=(const Element &a, const Element &b)
Test inequality.
Definition data.cc:272
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
std::vector< FunctionMap > HierarchyDescriptor
Hierarchy descriptor of the containers in a specific Element hierarchy tree.
Definition data.h:1119
boost::multiprecision::checked_int128_t int128_t
Definition bigints.h:19
Defines the logger used by the top-level component of kea-lfc.
Represents the position of the data element within a configuration string.
Definition data.h:107
uint32_t pos_
Position within the line.
Definition data.h:110
std::string str() const
Returns the position in the textual format.
Definition data.cc:44
uint32_t line_
Line number.
Definition data.h:109
std::string file_
File name.
Definition data.h:108