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