Kea 2.0.0
data.cc
Go to the documentation of this file.
1// Copyright (C) 2010-2021 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
11#include <cstring>
12#include <cassert>
13#include <climits>
14#include <list>
15#include <map>
16#include <cstdio>
17#include <iostream>
18#include <iomanip>
19#include <string>
20#include <sstream>
21#include <fstream>
22#include <cerrno>
23
24#include <boost/lexical_cast.hpp>
25
26#include <cmath>
27
28using namespace std;
29
30namespace {
31const char* const WHITESPACE = " \b\f\n\r\t";
32} // end anonymous namespace
33
34namespace isc {
35namespace data {
36
37std::string
39 std::ostringstream ss;
40 ss << file_ << ":" << line_ << ":" << pos_;
41 return (ss.str());
42}
43
44std::ostream&
45operator<<(std::ostream& out, const Element::Position& pos) {
46 out << pos.str();
47 return (out);
48}
49
50std::string
51Element::str() const {
52 std::stringstream ss;
53 toJSON(ss);
54 return (ss.str());
55}
56
57std::string
59 std::stringstream ss;
60 toJSON(ss);
61 return (ss.str());
62}
63
64void
65Element::toWire(std::ostream& ss) const {
66 toJSON(ss);
67}
68
69bool
70Element::getValue(int64_t&) const {
71 return (false);
72}
73
74bool
75Element::getValue(double&) const {
76 return (false);
77}
78
79bool
80Element::getValue(bool&) const {
81 return (false);
82}
83
84bool
85Element::getValue(std::string&) const {
86 return (false);
87}
88
89bool
90Element::getValue(std::vector<ElementPtr>&) const {
91 return (false);
92}
93
94bool
95Element::getValue(std::map<std::string, ConstElementPtr>&) const {
96 return (false);
97}
98
99bool
100Element::setValue(const long long int) {
101 return (false);
102}
103
104bool
105Element::setValue(const double) {
106 return (false);
107}
108
109bool
110Element::setValue(const bool) {
111 return (false);
112}
113
114bool
115Element::setValue(const std::string&) {
116 return (false);
117}
118
119bool
120Element::setValue(const std::vector<ElementPtr>&) {
121 return (false);
122}
123
124bool
125Element::setValue(const std::map<std::string, ConstElementPtr>&) {
126 return (false);
127}
128
130Element::get(const int) const {
131 throwTypeError("get(int) called on a non-container Element");
132}
133
135Element::getNonConst(const int) const {
136 throwTypeError("get(int) called on a non-container Element");
137}
138
139void
140Element::set(const size_t, ElementPtr) {
141 throwTypeError("set(int, element) called on a non-list Element");
142}
143
144void
146 throwTypeError("add() called on a non-list Element");
147}
148
149void
150Element::remove(const int) {
151 throwTypeError("remove(int) called on a non-container Element");
152}
153
154size_t
156 throwTypeError("size() called on a non-list Element");
157}
158
159bool
161 throwTypeError("empty() called on a non-container Element");
162}
163
165Element::get(const std::string&) const {
166 throwTypeError("get(string) called on a non-map Element");
167}
168
169void
170Element::set(const std::string&, ConstElementPtr) {
171 throwTypeError("set(name, element) called on a non-map Element");
172}
173
174void
175Element::remove(const std::string&) {
176 throwTypeError("remove(string) called on a non-map Element");
177}
178
179bool
180Element::contains(const std::string&) const {
181 throwTypeError("contains(string) called on a non-map Element");
182}
183
185Element::find(const std::string&) const {
186 throwTypeError("find(string) called on a non-map Element");
187}
188
189bool
190Element::find(const std::string&, ConstElementPtr&) const {
191 return (false);
192}
193
194namespace {
195inline void
196throwJSONError(const std::string& error, const std::string& file, int line,
197 int pos) {
198 std::stringstream ss;
199 ss << error << " in " + file + ":" << line << ":" << pos;
200 isc_throw(JSONError, ss.str());
201}
202} // end anonymous namespace
203
204std::ostream&
205operator<<(std::ostream& out, const Element& e) {
206 return (out << e.str());
207}
208
209bool
210operator==(const Element& a, const Element& b) {
211 return (a.equals(b));
212}
213
214bool operator!=(const Element& a, const Element& b) {
215 return (!a.equals(b));
216}
217
218bool
219operator<(Element const& a, Element const& b) {
220 if (a.getType() != b.getType()) {
221 isc_throw(BadValue, "cannot compare Elements of different types");
222 }
223 switch (a.getType()) {
224 case Element::integer:
225 return a.intValue() < b.intValue();
226 case Element::real:
227 return a.doubleValue() < b.doubleValue();
228 case Element::boolean:
229 return b.boolValue() || !a.boolValue();
230 case Element::string:
231 return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0;
232 }
233 isc_throw(BadValue, "cannot compare Elements of type " <<
234 std::to_string(a.getType()));
235}
236
237//
238// factory functions
239//
242 return (ElementPtr(new NullElement(pos)));
246Element::create(const long long int i, const Position& pos) {
247 return (ElementPtr(new IntElement(static_cast<int64_t>(i), pos)));
248}
249
251Element::create(const int i, const Position& pos) {
252 return (create(static_cast<long long int>(i), pos));
253}
254
256Element::create(const long int i, const Position& pos) {
257 return (create(static_cast<long long int>(i), pos));
258}
259
261Element::create(const double d, const Position& pos) {
262 return (ElementPtr(new DoubleElement(d, pos)));
266Element::create(const bool b, const Position& pos) {
267 return (ElementPtr(new BoolElement(b, pos)));
268}
269
271Element::create(const std::string& s, const Position& pos) {
272 return (ElementPtr(new StringElement(s, pos)));
273}
274
276Element::create(const char *s, const Position& pos) {
277 return (create(std::string(s), pos));
278}
279
282 return (ElementPtr(new ListElement(pos)));
283}
284
287 return (ElementPtr(new MapElement(pos)));
288}
289
290
291//
292// helper functions for fromJSON factory
293//
294namespace {
295bool
296charIn(const int c, const char* chars) {
297 const size_t chars_len = std::strlen(chars);
298 for (size_t i = 0; i < chars_len; ++i) {
299 if (chars[i] == c) {
300 return (true);
302 }
303 return (false);
304}
305
306void
307skipChars(std::istream& in, const char* chars, int& line, int& pos) {
308 int c = in.peek();
309 while (charIn(c, chars) && c != EOF) {
310 if (c == '\n') {
311 ++line;
312 pos = 1;
313 } else {
314 ++pos;
315 }
316 in.ignore();
317 c = in.peek();
318 }
320
321// skip on the input stream to one of the characters in chars
322// if another character is found this function throws JSONError
323// unless that character is specified in the optional may_skip
325// It returns the found character (as an int value).
326int
327skipTo(std::istream& in, const std::string& file, int& line, int& pos,
328 const char* chars, const char* may_skip="") {
329 int c = in.get();
330 ++pos;
331 while (c != EOF) {
332 if (c == '\n') {
333 pos = 1;
334 ++line;
335 }
336 if (charIn(c, may_skip)) {
337 c = in.get();
338 ++pos;
339 } else if (charIn(c, chars)) {
340 while (charIn(in.peek(), may_skip)) {
341 if (in.peek() == '\n') {
342 pos = 1;
343 ++line;
344 } else {
345 ++pos;
346 }
347 in.ignore();
348 }
349 return (c);
350 } else {
351 throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
352 }
353 }
354 throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
355 return (c); // shouldn't reach here, but some compilers require it
356}
357
358// TODO: Should we check for all other official escapes here (and
359// error on the rest)?
360std::string
361strFromStringstream(std::istream& in, const std::string& file,
362 const int line, int& pos) {
363 std::stringstream ss;
364 int c = in.get();
365 ++pos;
366 if (c == '"') {
367 c = in.get();
368 ++pos;
369 } else {
370 throwJSONError("String expected", file, line, pos);
371 }
372
373 while (c != EOF && c != '"') {
374 if (c == '\\') {
375 // see the spec for allowed escape characters
376 int d;
377 switch (in.peek()) {
378 case '"':
379 c = '"';
380 break;
381 case '/':
382 c = '/';
383 break;
384 case '\\':
385 c = '\\';
386 break;
387 case 'b':
388 c = '\b';
389 break;
390 case 'f':
391 c = '\f';
392 break;
393 case 'n':
394 c = '\n';
395 break;
396 case 'r':
397 c = '\r';
398 break;
399 case 't':
400 c = '\t';
401 break;
402 case 'u':
403 // skip first 0
404 in.ignore();
405 ++pos;
406 c = in.peek();
407 if (c != '0') {
408 throwJSONError("Unsupported unicode escape", file, line, pos);
409 }
410 // skip second 0
411 in.ignore();
412 ++pos;
413 c = in.peek();
414 if (c != '0') {
415 throwJSONError("Unsupported unicode escape", file, line, pos - 2);
416 }
417 // get first digit
418 in.ignore();
419 ++pos;
420 d = in.peek();
421 if ((d >= '0') && (d <= '9')) {
422 c = (d - '0') << 4;
423 } else if ((d >= 'A') && (d <= 'F')) {
424 c = (d - 'A' + 10) << 4;
425 } else if ((d >= 'a') && (d <= 'f')) {
426 c = (d - 'a' + 10) << 4;
427 } else {
428 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3);
429 }
430 // get second digit
431 in.ignore();
432 ++pos;
433 d = in.peek();
434 if ((d >= '0') && (d <= '9')) {
435 c |= d - '0';
436 } else if ((d >= 'A') && (d <= 'F')) {
437 c |= d - 'A' + 10;
438 } else if ((d >= 'a') && (d <= 'f')) {
439 c |= d - 'a' + 10;
440 } else {
441 throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4);
442 }
443 break;
444 default:
445 throwJSONError("Bad escape", file, line, pos);
446 }
447 // drop the escaped char
448 in.ignore();
449 ++pos;
450 }
451 ss.put(c);
452 c = in.get();
453 ++pos;
454 }
455 if (c == EOF) {
456 throwJSONError("Unterminated string", file, line, pos);
457 }
458 return (ss.str());
459}
460
461std::string
462wordFromStringstream(std::istream& in, int& pos) {
463 std::stringstream ss;
464 while (isalpha(in.peek())) {
465 ss << (char) in.get();
466 }
467 pos += ss.str().size();
468 return (ss.str());
469}
470
471std::string
472numberFromStringstream(std::istream& in, int& pos) {
473 std::stringstream ss;
474 while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
475 in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
476 ss << (char) in.get();
477 }
478 pos += ss.str().size();
479 return (ss.str());
480}
481
482// Should we change from IntElement and DoubleElement to NumberElement
483// that can also hold an e value? (and have specific getters if the
484// value is larger than an int can handle)
485//
487fromStringstreamNumber(std::istream& in, const std::string& file,
488 const int line, int& pos) {
489 // Remember position where the value starts. It will be set in the
490 // Position structure of the Element to be created.
491 const uint32_t start_pos = pos;
492 // This will move the pos to the end of the value.
493 const std::string number = numberFromStringstream(in, pos);
494
495 if (number.find_first_of(".eE") < number.size()) {
496 try {
497 return (Element::create(boost::lexical_cast<double>(number),
498 Element::Position(file, line, start_pos)));
499 } catch (const boost::bad_lexical_cast&) {
500 throwJSONError(std::string("Number overflow: ") + number,
501 file, line, start_pos);
502 }
503 } else {
504 try {
505 return (Element::create(boost::lexical_cast<int64_t>(number),
506 Element::Position(file, line, start_pos)));
507 } catch (const boost::bad_lexical_cast&) {
508 throwJSONError(std::string("Number overflow: ") + number, file,
509 line, start_pos);
510 }
511 }
512 return (ElementPtr());
513}
514
516fromStringstreamBool(std::istream& in, const std::string& file,
517 const int line, int& pos) {
518 // Remember position where the value starts. It will be set in the
519 // Position structure of the Element to be created.
520 const uint32_t start_pos = pos;
521 // This will move the pos to the end of the value.
522 const std::string word = wordFromStringstream(in, pos);
523
524 if (word == "true") {
525 return (Element::create(true, Element::Position(file, line,
526 start_pos)));
527 } else if (word == "false") {
528 return (Element::create(false, Element::Position(file, line,
529 start_pos)));
530 } else {
531 throwJSONError(std::string("Bad boolean value: ") + word, file,
532 line, start_pos);
533 }
534 return (ElementPtr());
535}
536
538fromStringstreamNull(std::istream& in, const std::string& file,
539 const int line, int& pos) {
540 // Remember position where the value starts. It will be set in the
541 // Position structure of the Element to be created.
542 const uint32_t start_pos = pos;
543 // This will move the pos to the end of the value.
544 const std::string word = wordFromStringstream(in, pos);
545 if (word == "null") {
546 return (Element::create(Element::Position(file, line, start_pos)));
547 } else {
548 throwJSONError(std::string("Bad null value: ") + word, file,
549 line, start_pos);
550 return (ElementPtr());
551 }
552}
553
555fromStringstreamString(std::istream& in, const std::string& file, int& line,
556 int& pos) {
557 // Remember position where the value starts. It will be set in the
558 // Position structure of the Element to be created.
559 const uint32_t start_pos = pos;
560 // This will move the pos to the end of the value.
561 const std::string string_value = strFromStringstream(in, file, line, pos);
562 return (Element::create(string_value, Element::Position(file, line,
563 start_pos)));
564}
565
567fromStringstreamList(std::istream& in, const std::string& file, int& line,
568 int& pos) {
569 int c = 0;
570 ElementPtr list = Element::createList(Element::Position(file, line, pos));
571 ElementPtr cur_list_element;
572
573 skipChars(in, WHITESPACE, line, pos);
574 while (c != EOF && c != ']') {
575 if (in.peek() != ']') {
576 cur_list_element = Element::fromJSON(in, file, line, pos);
577 list->add(cur_list_element);
578 c = skipTo(in, file, line, pos, ",]", WHITESPACE);
579 } else {
580 c = in.get();
581 ++pos;
582 }
583 }
584 return (list);
585}
586
588fromStringstreamMap(std::istream& in, const std::string& file, int& line,
589 int& pos) {
590 ElementPtr map = Element::createMap(Element::Position(file, line, pos));
591 skipChars(in, WHITESPACE, line, pos);
592 int c = in.peek();
593 if (c == EOF) {
594 throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
595 } else if (c == '}') {
596 // empty map, skip closing curly
597 in.ignore();
598 } else {
599 while (c != EOF && c != '}') {
600 std::string key = strFromStringstream(in, file, line, pos);
601
602 skipTo(in, file, line, pos, ":", WHITESPACE);
603 // skip the :
604
605 ConstElementPtr value = Element::fromJSON(in, file, line, pos);
606 map->set(key, value);
607
608 c = skipTo(in, file, line, pos, ",}", WHITESPACE);
609 }
610 }
611 return (map);
612}
613} // end anonymous namespace
614
615std::string
617 switch (type) {
618 case Element::integer:
619 return (std::string("integer"));
620 case Element::real:
621 return (std::string("real"));
622 case Element::boolean:
623 return (std::string("boolean"));
624 case Element::string:
625 return (std::string("string"));
626 case Element::list:
627 return (std::string("list"));
628 case Element::map:
629 return (std::string("map"));
630 case Element::null:
631 return (std::string("null"));
632 case Element::any:
633 return (std::string("any"));
634 default:
635 return (std::string("unknown"));
636 }
637}
638
640Element::nameToType(const std::string& type_name) {
641 if (type_name == "integer") {
642 return (Element::integer);
643 } else if (type_name == "real") {
644 return (Element::real);
645 } else if (type_name == "boolean") {
646 return (Element::boolean);
647 } else if (type_name == "string") {
648 return (Element::string);
649 } else if (type_name == "list") {
650 return (Element::list);
651 } else if (type_name == "map") {
652 return (Element::map);
653 } else if (type_name == "named_set") {
654 return (Element::map);
655 } else if (type_name == "null") {
656 return (Element::null);
657 } else if (type_name == "any") {
658 return (Element::any);
659 } else {
660 isc_throw(TypeError, type_name + " is not a valid type name");
661 }
662}
663
665Element::fromJSON(std::istream& in, bool preproc) {
666
667 int line = 1, pos = 1;
668 stringstream filtered;
669 if (preproc) {
670 preprocess(in, filtered);
671 }
672
673 ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
674
675 return (value);
676}
677
679Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) {
680 int line = 1, pos = 1;
681 stringstream filtered;
682 if (preproc) {
683 preprocess(in, filtered);
684 }
685 return (fromJSON(preproc ? filtered : in, file_name, line, pos));
686}
687
689Element::fromJSON(std::istream& in, const std::string& file, int& line,
690 int& pos) {
691 int c = 0;
692 ElementPtr element;
693 bool el_read = false;
694 skipChars(in, WHITESPACE, line, pos);
695 while (c != EOF && !el_read) {
696 c = in.get();
697 pos++;
698 switch(c) {
699 case '1':
700 case '2':
701 case '3':
702 case '4':
703 case '5':
704 case '6':
705 case '7':
706 case '8':
707 case '9':
708 case '0':
709 case '-':
710 case '+':
711 case '.':
712 in.putback(c);
713 --pos;
714 element = fromStringstreamNumber(in, file, line, pos);
715 el_read = true;
716 break;
717 case 't':
718 case 'f':
719 in.putback(c);
720 --pos;
721 element = fromStringstreamBool(in, file, line, pos);
722 el_read = true;
723 break;
724 case 'n':
725 in.putback(c);
726 --pos;
727 element = fromStringstreamNull(in, file, line, pos);
728 el_read = true;
729 break;
730 case '"':
731 in.putback('"');
732 --pos;
733 element = fromStringstreamString(in, file, line, pos);
734 el_read = true;
735 break;
736 case '[':
737 element = fromStringstreamList(in, file, line, pos);
738 el_read = true;
739 break;
740 case '{':
741 element = fromStringstreamMap(in, file, line, pos);
742 el_read = true;
743 break;
744 case EOF:
745 break;
746 default:
747 throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
748 break;
749 }
750 }
751 if (el_read) {
752 return (element);
753 } else {
754 isc_throw(JSONError, "nothing read");
755 }
756}
757
759Element::fromJSON(const std::string& in, bool preproc) {
760 std::stringstream ss;
761 ss << in;
762
763 int line = 1, pos = 1;
764 stringstream filtered;
765 if (preproc) {
766 preprocess(ss, filtered);
767 }
768 ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
769 skipChars(ss, WHITESPACE, line, pos);
770 // ss must now be at end
771 if (ss.peek() != EOF) {
772 throwJSONError("Extra data", "<string>", line, pos);
773 }
774 return result;
775}
776
778Element::fromJSONFile(const std::string& file_name, bool preproc) {
779 // zero out the errno to be safe
780 errno = 0;
781
782 std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
783 if (!infile.is_open()) {
784 const char* error = strerror(errno);
785 isc_throw(InvalidOperation, "failed to read file '" << file_name
786 << "': " << error);
787 }
788
789 return (fromJSON(infile, file_name, preproc));
790}
791
792// to JSON format
793
794void
795IntElement::toJSON(std::ostream& ss) const {
796 ss << intValue();
797}
798
799void
800DoubleElement::toJSON(std::ostream& ss) const {
801 // The default output for doubles nicely drops off trailing
802 // zeros, however this produces strings without decimal points
803 // for whole number values. When reparsed this will create
804 // IntElements not DoubleElements. Rather than used a fixed
805 // precision, we'll just tack on an ".0" when the decimal point
806 // is missing.
807 ostringstream val_ss;
808 val_ss << doubleValue();
809 ss << val_ss.str();
810 if (val_ss.str().find_first_of('.') == string::npos) {
811 ss << ".0";
812 }
813}
814
815void
816BoolElement::toJSON(std::ostream& ss) const {
817 if (boolValue()) {
818 ss << "true";
819 } else {
820 ss << "false";
821 }
822}
823
824void
825NullElement::toJSON(std::ostream& ss) const {
826 ss << "null";
827}
828
829void
830StringElement::toJSON(std::ostream& ss) const {
831 ss << "\"";
832 const std::string& str = stringValue();
833 for (size_t i = 0; i < str.size(); ++i) {
834 const char c = str[i];
835 // Escape characters as defined in JSON spec
836 // Note that we do not escape forward slash; this
837 // is allowed, but not mandatory.
838 switch (c) {
839 case '"':
840 ss << '\\' << c;
841 break;
842 case '\\':
843 ss << '\\' << c;
844 break;
845 case '\b':
846 ss << '\\' << 'b';
847 break;
848 case '\f':
849 ss << '\\' << 'f';
850 break;
851 case '\n':
852 ss << '\\' << 'n';
853 break;
854 case '\r':
855 ss << '\\' << 'r';
856 break;
857 case '\t':
858 ss << '\\' << 't';
859 break;
860 default:
861 if (((c >= 0) && (c < 0x20)) || (c < 0) || (c >= 0x7f)) {
862 std::ostringstream esc;
863 esc << "\\u"
864 << hex
865 << setw(4)
866 << setfill('0')
867 << (static_cast<unsigned>(c) & 0xff);
868 ss << esc.str();
869 } else {
870 ss << c;
871 }
872 }
873 }
874 ss << "\"";
875}
876
877void
878ListElement::toJSON(std::ostream& ss) const {
879 ss << "[ ";
880
881 const std::vector<ElementPtr>& v = listValue();
882 for (auto it = v.begin(); it != v.end(); ++it) {
883 if (it != v.begin()) {
884 ss << ", ";
885 }
886 (*it)->toJSON(ss);
887 }
888 ss << " ]";
889}
890
891void
892MapElement::toJSON(std::ostream& ss) const {
893 ss << "{ ";
894
895 const std::map<std::string, ConstElementPtr>& m = mapValue();
896 for (auto it = m.begin(); it != m.end(); ++it) {
897 if (it != m.begin()) {
898 ss << ", ";
899 }
900 ss << "\"" << (*it).first << "\": ";
901 if ((*it).second) {
902 (*it).second->toJSON(ss);
903 } else {
904 ss << "None";
905 }
906 }
907 ss << " }";
908}
909
910// throws when one of the types in the path (except the one
911// we're looking for) is not a MapElement
912// returns 0 if it could simply not be found
913// should that also be an exception?
915MapElement::find(const std::string& id) const {
916 const size_t sep = id.find('/');
917 if (sep == std::string::npos) {
918 return (get(id));
919 } else {
920 ConstElementPtr ce = get(id.substr(0, sep));
921 if (ce) {
922 // ignore trailing slash
923 if (sep + 1 != id.size()) {
924 return (ce->find(id.substr(sep + 1)));
925 } else {
926 return (ce);
927 }
928 } else {
929 return (ElementPtr());
930 }
931 }
932}
933
935Element::fromWire(const std::string& s) {
936 std::stringstream ss;
937 ss << s;
938 int line = 0, pos = 0;
939 return (fromJSON(ss, "<wire>", line, pos));
940}
941
943Element::fromWire(std::stringstream& in, int) {
944 //
945 // Check protocol version
946 //
947 //for (int i = 0 ; i < 4 ; ++i) {
948 // const unsigned char version_byte = get_byte(in);
949 // if (PROTOCOL_VERSION[i] != version_byte) {
950 // throw DecodeError("Protocol version incorrect");
951 // }
952 //}
953 //length -= 4;
954 int line = 0, pos = 0;
955 return (fromJSON(in, "<wire>", line, pos));
956}
957
958void
959MapElement::set(const std::string& key, ConstElementPtr value) {
960 m[key] = value;
961}
962
963bool
964MapElement::find(const std::string& id, ConstElementPtr& t) const {
965 try {
966 ConstElementPtr p = find(id);
967 if (p) {
968 t = p;
969 return (true);
970 }
971 } catch (const TypeError&) {
972 // ignore
973 }
974 return (false);
975}
976
977bool
978IntElement::equals(const Element& other) const {
979 return (other.getType() == Element::integer) &&
980 (i == other.intValue());
981}
982
983bool
984DoubleElement::equals(const Element& other) const {
985 return (other.getType() == Element::real) &&
986 (fabs(d - other.doubleValue()) < 1e-14);
987}
988
989bool
990BoolElement::equals(const Element& other) const {
991 return (other.getType() == Element::boolean) &&
992 (b == other.boolValue());
993}
994
995bool
996NullElement::equals(const Element& other) const {
997 return (other.getType() == Element::null);
998}
999
1000bool
1001StringElement::equals(const Element& other) const {
1002 return (other.getType() == Element::string) &&
1003 (s == other.stringValue());
1004}
1005
1006bool
1007ListElement::equals(const Element& other) const {
1008 if (other.getType() == Element::list) {
1009 const size_t s = size();
1010 if (s != other.size()) {
1011 return (false);
1012 }
1013 for (size_t i = 0; i < s; ++i) {
1014 if (!get(i)->equals(*other.get(i))) {
1015 return (false);
1016 }
1017 }
1018 return (true);
1019 } else {
1020 return (false);
1021 }
1022}
1023
1024void
1025ListElement::sort(std::string const& index /* = std::string() */) {
1026 if (l.empty()) {
1027 return;
1028 }
1029
1030 int const t(l.at(0)->getType());
1031 std::function<bool(ElementPtr, ElementPtr)> comparator;
1032 if (t == map) {
1033 if (index.empty()) {
1034 isc_throw(BadValue, "index required when sorting maps");
1035 }
1036 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1037 ConstElementPtr const& ai(a->get(index));
1038 ConstElementPtr const& bi(b->get(index));
1039 if (ai && bi) {
1040 return *ai < *bi;
1041 }
1042 return true;
1043 };
1044 } else if (t == list) {
1045 // Nested lists. Not supported.
1046 return;
1047 } else {
1048 // Assume scalars.
1049 if (!index.empty()) {
1050 isc_throw(BadValue, "index given when sorting scalars?");
1051 }
1052 comparator = [&](ElementPtr const& a, ElementPtr const& b) {
1053 return *a < *b;
1054 };
1055 }
1056
1057 std::sort(l.begin(), l.end(), comparator);
1058}
1059
1060bool
1061MapElement::equals(const Element& other) const {
1062 if (other.getType() == Element::map) {
1063 if (size() != other.size()) {
1064 return (false);
1065 }
1066 for (auto kv : mapValue()) {
1067 auto key = kv.first;
1068 if (other.contains(key)) {
1069 if (!get(key)->equals(*other.get(key))) {
1070 return (false);
1071 }
1072 } else {
1073 return (false);
1074 }
1075 }
1076 return (true);
1077 } else {
1078 return (false);
1079 }
1080}
1081
1082bool
1084 return (!p);
1085}
1086
1087void
1089 if (!b) {
1090 return;
1091 }
1092 if (a->getType() != Element::map || b->getType() != Element::map) {
1093 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1094 }
1095
1096 // As maps do not allow entries with multiple keys, we can either iterate
1097 // over a checking for identical entries in b or vice-versa. As elements
1098 // are removed from a if a match is found, we choose to iterate over b to
1099 // avoid problems with element removal affecting the iterator.
1100 for (auto kv : b->mapValue()) {
1101 auto key = kv.first;
1102 if (a->contains(key)) {
1103 if (a->get(key)->equals(*b->get(key))) {
1104 a->remove(key);
1105 }
1106 }
1107 }
1108}
1109
1112 ElementPtr result = Element::createMap();
1113
1114 if (!b) {
1115 return (result);
1116 }
1117
1118 if (a->getType() != Element::map || b->getType() != Element::map) {
1119 isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
1120 }
1121
1122 for (auto kv : a->mapValue()) {
1123 auto key = kv.first;
1124 if (!b->contains(key) ||
1125 !a->get(key)->equals(*b->get(key))) {
1126 result->set(key, kv.second);
1127 }
1128 }
1129
1130 return (result);
1131}
1132
1133void
1135 if (element->getType() != Element::map ||
1136 other->getType() != Element::map) {
1137 isc_throw(TypeError, "merge arguments not MapElements");
1138 }
1139
1140 for (auto kv : other->mapValue()) {
1141 auto key = kv.first;
1142 auto value = kv.second;
1143 if (value && value->getType() != Element::null) {
1144 element->set(key, value);
1145 } else if (element->contains(key)) {
1146 element->remove(key);
1147 }
1148 }
1149}
1150
1152copy(ConstElementPtr from, int level) {
1153 if (!from) {
1154 isc_throw(BadValue, "copy got a null pointer");
1155 }
1156 int from_type = from->getType();
1157 if (from_type == Element::integer) {
1158 return (ElementPtr(new IntElement(from->intValue())));
1159 } else if (from_type == Element::real) {
1160 return (ElementPtr(new DoubleElement(from->doubleValue())));
1161 } else if (from_type == Element::boolean) {
1162 return (ElementPtr(new BoolElement(from->boolValue())));
1163 } else if (from_type == Element::null) {
1164 return (ElementPtr(new NullElement()));
1165 } else if (from_type == Element::string) {
1166 return (ElementPtr(new StringElement(from->stringValue())));
1167 } else if (from_type == Element::list) {
1168 ElementPtr result = ElementPtr(new ListElement());
1169 for (auto elem : from->listValue()) {
1170 if (level == 0) {
1171 result->add(elem);
1172 } else {
1173 result->add(copy(elem, level - 1));
1174 }
1175 }
1176 return (result);
1177 } else if (from_type == Element::map) {
1178 ElementPtr result = ElementPtr(new MapElement());
1179 for (auto kv : from->mapValue()) {
1180 auto key = kv.first;
1181 auto value = kv.second;
1182 if (level == 0) {
1183 result->set(key, value);
1184 } else {
1185 result->set(key, copy(value, level - 1));
1186 }
1187 }
1188 return (result);
1189 } else {
1190 isc_throw(BadValue, "copy got an element of type: " << from_type);
1191 }
1192}
1193
1194namespace {
1195
1196// Helper function which blocks infinite recursion
1197bool
1198isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) {
1199 // check looping forever on cycles
1200 if (!level) {
1201 isc_throw(BadValue, "isEquivalent got infinite recursion: "
1202 "arguments include cycles");
1203 }
1204 if (!a || !b) {
1205 isc_throw(BadValue, "isEquivalent got a null pointer");
1206 }
1207 // check types
1208 if (a->getType() != b->getType()) {
1209 return (false);
1210 }
1211 if (a->getType() == Element::list) {
1212 // check empty
1213 if (a->empty()) {
1214 return (b->empty());
1215 }
1216 // check size
1217 if (a->size() != b->size()) {
1218 return (false);
1219 }
1220
1221 // copy b into a list
1222 const size_t s = a->size();
1223 std::list<ConstElementPtr> l;
1224 for (size_t i = 0; i < s; ++i) {
1225 l.push_back(b->get(i));
1226 }
1227
1228 // iterate on a
1229 for (size_t i = 0; i < s; ++i) {
1230 ConstElementPtr item = a->get(i);
1231 // lookup this item in the list
1232 bool found = false;
1233 for (auto it = l.begin(); it != l.end(); ++it) {
1234 // if found in the list remove it
1235 if (isEquivalent0(item, *it, level - 1)) {
1236 found = true;
1237 l.erase(it);
1238 break;
1239 }
1240 }
1241 // if not found argument differs
1242 if (!found) {
1243 return (false);
1244 }
1245 }
1246
1247 // sanity check: the list must be empty
1248 if (!l.empty()) {
1249 isc_throw(Unexpected, "isEquivalent internal error");
1250 }
1251 return (true);
1252 } else if (a->getType() == Element::map) {
1253 // check sizes
1254 if (a->size() != b->size()) {
1255 return (false);
1256 }
1257 // iterate on the first map
1258 for (auto kv : a->mapValue()) {
1259 // get the b value for the given keyword and recurse
1260 ConstElementPtr item = b->get(kv.first);
1261 if (!item || !isEquivalent0(kv.second, item, level - 1)) {
1262 return (false);
1263 }
1264 }
1265 return (true);
1266 } else {
1267 return (a->equals(*b));
1268 }
1269}
1270
1271} // end anonymous namespace
1272
1273bool
1275 return (isEquivalent0(a, b, 100));
1276}
1277
1278void
1279prettyPrint(ConstElementPtr element, std::ostream& out,
1280 unsigned indent, unsigned step) {
1281 if (!element) {
1282 isc_throw(BadValue, "prettyPrint got a null pointer");
1283 }
1284 if (element->getType() == Element::list) {
1285 // empty list case
1286 if (element->empty()) {
1287 out << "[ ]";
1288 return;
1289 }
1290
1291 // complex ? multiline : oneline
1292 if (!element->get(0)) {
1293 isc_throw(BadValue, "prettyPrint got a null pointer");
1294 }
1295 int first_type = element->get(0)->getType();
1296 bool complex = false;
1297 if ((first_type == Element::list) || (first_type == Element::map)) {
1298 complex = true;
1299 }
1300 std::string separator = complex ? ",\n" : ", ";
1301
1302 // open the list
1303 out << "[" << (complex ? "\n" : " ");
1304
1305 // iterate on items
1306 const auto& l = element->listValue();
1307 for (auto it = l.begin(); it != l.end(); ++it) {
1308 // add the separator if not the first item
1309 if (it != l.begin()) {
1310 out << separator;
1311 }
1312 // add indentation
1313 if (complex) {
1314 out << std::string(indent + step, ' ');
1315 }
1316 // recursive call
1317 prettyPrint(*it, out, indent + step, step);
1318 }
1319
1320 // close the list
1321 if (complex) {
1322 out << "\n" << std::string(indent, ' ');
1323 } else {
1324 out << " ";
1325 }
1326 out << "]";
1327 } else if (element->getType() == Element::map) {
1328 // empty map case
1329 if (element->size() == 0) {
1330 out << "{ }";
1331 return;
1332 }
1333
1334 // open the map
1335 out << "{\n";
1336
1337 // iterate on keyword: value
1338 const auto& m = element->mapValue();
1339 bool first = true;
1340 for (auto it = m.begin(); it != m.end(); ++it) {
1341 // add the separator if not the first item
1342 if (first) {
1343 first = false;
1344 } else {
1345 out << ",\n";
1346 }
1347 // add indentation
1348 out << std::string(indent + step, ' ');
1349 // add keyword:
1350 out << "\"" << it->first << "\": ";
1351 // recursive call
1352 prettyPrint(it->second, out, indent + step, step);
1353 }
1354
1355 // close the map
1356 out << "\n" << std::string(indent, ' ') << "}";
1357 } else {
1358 // not a list or a map
1359 element->toJSON(out);
1360 }
1361}
1362
1363std::string
1364prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
1365 std::stringstream ss;
1366 prettyPrint(element, ss, indent, step);
1367 return (ss.str());
1368}
1369
1370void Element::preprocess(std::istream& in, std::stringstream& out) {
1371
1372 std::string line;
1373
1374 while (std::getline(in, line)) {
1375 // If this is a comments line, replace it with empty line
1376 // (so the line numbers will still match
1377 if (!line.empty() && line[0] == '#') {
1378 line = "";
1379 }
1380
1381 // getline() removes end line characters. Unfortunately, we need
1382 // it for getting the line numbers right (in case we report an
1383 // error.
1384 out << line;
1385 out << "\n";
1386 }
1387}
1388
1389} // end of isc::data namespace
1390} // end of isc namespace
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.
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:816
bool equals(const Element &other) const
Definition: data.cc:990
bool equals(const Element &other) const
Definition: data.cc:984
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:800
The Element class represents a piece of data, used by the command channel and configuration parts.
Definition: data.h:70
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:241
virtual bool equals(const Element &other) const =0
virtual bool getValue(int64_t &t) const
Definition: data.cc:70
static std::string typeToName(Element::types type)
Returns the name of the given type as a string.
Definition: data.cc:616
virtual int64_t intValue() const
Definition: data.h:215
std::string str() const
Returns a string representing the Element and all its child elements; note that this is different fro...
Definition: data.cc:51
virtual std::string stringValue() const
Definition: data.h:221
std::string toWire() const
Returns the wireformat for the Element and all its child elements.
Definition: data.cc:58
static ElementPtr fromWire(std::stringstream &in, int length)
These function pparse the wireformat at the given stringstream (of the given length).
Definition: data.cc:943
virtual bool setValue(const long long int v)
Definition: data.cc:100
static ElementPtr fromJSONFile(const std::string &file_name, bool preproc=false)
Reads contents of specified file and interprets it as JSON.
Definition: data.cc:778
virtual bool empty() const
Return true if there are no elements in the list.
Definition: data.cc:160
virtual void remove(const int i)
Removes the element at the given position.
Definition: data.cc:150
virtual bool contains(const std::string &name) const
Checks if there is data at the given key.
Definition: data.cc:180
virtual ConstElementPtr find(const std::string &identifier) const
Recursively finds any data at the given identifier.
Definition: data.cc:185
virtual size_t size() const
Returns the number of elements in the list.
Definition: data.cc:155
virtual const std::map< std::string, ConstElementPtr > & mapValue() const
Definition: data.h:227
virtual void add(ElementPtr element)
Adds an ElementPtr to the list.
Definition: data.cc:145
virtual const std::vector< ElementPtr > & listValue() const
Definition: data.h:223
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:759
virtual ConstElementPtr get(const int i) const
Returns the ElementPtr at the given index.
Definition: data.cc:130
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:286
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:640
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:281
virtual void toJSON(std::ostream &ss) const =0
Converts the Element to JSON format and appends it to the given stringstream.
virtual void set(const size_t i, ElementPtr element)
Sets the ElementPtr at the given index.
Definition: data.cc:140
virtual double doubleValue() const
Definition: data.h:217
int getType() const
Definition: data.h:160
virtual bool boolValue() const
Definition: data.h:219
static void preprocess(std::istream &in, std::stringstream &out)
input text preprocessor
Definition: data.cc:1370
virtual ElementPtr getNonConst(const int i) const
returns element as non-const pointer
Definition: data.cc:135
Notes: IntElement type is changed to int64_t.
Definition: data.h:588
bool equals(const Element &other) const
Definition: data.cc:978
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:795
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition: data.h:47
void sort(std::string const &index=std::string())
Sorts the elements inside the list.
Definition: data.cc:1025
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:878
bool equals(const Element &other) const
Definition: data.cc:1007
ConstElementPtr find(const std::string &id) const override
Recursively finds any data at the given identifier.
Definition: data.cc:915
void set(const std::string &key, ConstElementPtr value) override
Sets the ElementPtr at the given key.
Definition: data.cc:959
bool equals(const Element &other) const override
Definition: data.cc:1061
void toJSON(std::ostream &ss) const override
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:892
bool equals(const Element &other) const
Definition: data.cc:996
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:825
void toJSON(std::ostream &ss) const
Converts the Element to JSON format and appends it to the given stringstream.
Definition: data.cc:830
bool equals(const Element &other) const
Definition: data.cc:1001
A standard Data module exception that is thrown if a function is called for an Element that has a wro...
Definition: data.h:34
#define throwTypeError(error)
Add the position to a TypeError message should be used in place of isc_throw(TypeError,...
Definition: data.h:187
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
ElementPtr copy(ConstElementPtr from, int level)
Copy the data up to a nesting level.
Definition: data.cc:1152
bool operator==(const Element &a, const Element &b)
Definition: data.cc:210
void removeIdentical(ElementPtr a, ConstElementPtr b)
Remove all values from the first ElementPtr that are equal in the second.
Definition: data.cc:1088
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element.
Definition: data.cc:1134
bool operator<(Element const &a, Element const &b)
Definition: data.cc:219
bool isEquivalent(ConstElementPtr a, ConstElementPtr b)
Compares the data with other using unordered lists.
Definition: data.cc:1274
void prettyPrint(ConstElementPtr element, std::ostream &out, unsigned indent, unsigned step)
Pretty prints the data into stream.
Definition: data.cc:1279
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
bool isNull(ConstElementPtr p)
Checks whether the given ElementPtr is a NULL pointer.
Definition: data.cc:1083
std::ostream & operator<<(std::ostream &out, const Element::Position &pos)
Insert Element::Position as a string into stream.
Definition: data.cc:45
bool operator!=(const Element &a, const Element &b)
Definition: data.cc:214
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
@ error
Definition: db_log.h:118
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:92
uint32_t pos_
Position within the line.
Definition: data.h:95
std::string str() const
Returns the position in the textual format.
Definition: data.cc:38
uint32_t line_
Line number.
Definition: data.h:94
std::string file_
File name.
Definition: data.h:93