123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879 |
- // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- // PERFORMANCE OF THIS SOFTWARE.
- // $Id$
- #include <config.h>
- #include <cc/data.h>
- #include <cassert>
- #include <climits>
- #include <cstdio>
- #include <iostream>
- #include <string>
- #include <sstream>
- #include <boost/algorithm/string.hpp> // for iequals
- #include <cmath>
- using namespace std;
- namespace isc {
- namespace data {
- std::string
- Element::str() {
- std::stringstream ss;
- toJSON(ss);
- return (ss.str());
- }
- std::string
- Element::toWire() {
- std::stringstream ss;
- toJSON(ss);
- return (ss.str());
- }
- void
- Element::toWire(std::ostream& ss) {
- toJSON(ss);
- }
- //
- // The following methods are effectively empty, and their parameters are
- // unused. To silence compilers that warn unused function parameters,
- // we specify a (compiler dependent) special keyword when available.
- // It's defined in config.h, and to avoid including this header file from
- // installed files we define the methods here.
- //
- bool
- Element::getValue(long int& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::getValue(double& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::getValue(bool& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::getValue(std::string& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::getValue(std::vector<ElementPtr>& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::getValue(std::map<std::string, ElementPtr>& t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const long int v UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const double v UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const bool t UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const std::string& v UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const std::vector<ElementPtr>& v UNUSED_PARAM) {
- return (false);
- }
- bool
- Element::setValue(const std::map<std::string, ElementPtr>& v UNUSED_PARAM) {
- return (false);
- }
- ElementPtr
- Element::get(const int i UNUSED_PARAM) {
- isc_throw(TypeError, "get(int) called on a non-list Element");
- }
- void
- Element::set(const size_t i UNUSED_PARAM, ElementPtr element UNUSED_PARAM) {
- isc_throw(TypeError, "set(int, element) called on a non-list Element");
- }
- void
- Element::add(ElementPtr element UNUSED_PARAM) {
- isc_throw(TypeError, "add() called on a non-list Element");
- }
- void
- Element::remove(const int i UNUSED_PARAM) {
- isc_throw(TypeError, "remove(int) called on a non-list Element");
- }
- size_t
- Element::size() {
- isc_throw(TypeError, "size() called on a non-list Element");
- }
- ElementPtr
- Element::get(const std::string& name UNUSED_PARAM) {
- isc_throw(TypeError, "get(string) called on a non-map Element");
- }
- void
- Element::set(const std::string& name UNUSED_PARAM,
- ElementPtr element UNUSED_PARAM)
- {
- isc_throw(TypeError, "set(name, element) called on a non-map Element");
- }
- void
- Element::remove(const std::string& name UNUSED_PARAM) {
- isc_throw(TypeError, "remove(string) called on a non-map Element");
- }
- bool
- Element::contains(const std::string& name UNUSED_PARAM) {
- isc_throw(TypeError, "contains(string) called on a non-map Element");
- }
- ElementPtr
- Element::find(const std::string& identifier UNUSED_PARAM) {
- isc_throw(TypeError, "find(string) called on a non-map Element");
- }
- bool
- Element::find(const std::string& identifier UNUSED_PARAM,
- ElementPtr& t UNUSED_PARAM)
- {
- return (false);
- }
- namespace {
- inline void
- throwJSONError(const std::string& error, const std::string& file, int line, int pos)
- {
- std::stringstream ss;
- ss << error << " in " + file + ":" << line << ":" << pos;
- isc_throw(JSONError, ss.str());
- }
- }
- std::ostream& operator <<(std::ostream &out, const isc::data::ElementPtr& e) {
- return (out << e->str());
- }
- bool operator==(const isc::data::ElementPtr a, const isc::data::ElementPtr b) {
- return (a->equals(b));
- };
- //
- // factory functions
- //
- ElementPtr
- Element::create() {
- return (ElementPtr(new NullElement()));
- }
- ElementPtr
- Element::create(const long int i) {
- return (ElementPtr(new IntElement(i)));
- }
- ElementPtr
- Element::create(const double d) {
- return (ElementPtr(new DoubleElement(d)));
- }
- ElementPtr
- Element::create(const std::string& s) {
- return (ElementPtr(new StringElement(s)));
- }
- ElementPtr
- Element::create(const bool b) {
- return (ElementPtr(new BoolElement(b)));
- }
- ElementPtr
- Element::createList() {
- return (ElementPtr(new ListElement()));
- }
- ElementPtr
- Element::createMap() {
- return (ElementPtr(new MapElement()));
- }
- //
- // helper functions for fromJSON factory
- //
- namespace {
- bool
- char_in(const char c, const char *chars) {
- for (size_t i = 0; i < strlen(chars); ++i) {
- if (chars[i] == c) {
- return (true);
- }
- }
- return (false);
- }
- void
- skip_chars(std::istream &in, const char *chars, int& line, int& pos) {
- char c = in.peek();
- while (char_in(c, chars) && c != EOF) {
- if (c == '\n') {
- ++line;
- pos = 1;
- } else {
- ++pos;
- }
- in.get();
- c = in.peek();
- }
- }
- // skip on the input stream to one of the characters in chars
- // if another character is found this function returns false
- // unless that character is specified in the optional may_skip
- //
- // the character found is left on the stream
- void
- skip_to(std::istream &in, const std::string& file, int& line,
- int& pos, const char* chars, const char* may_skip="")
- {
- char c = in.get();
- ++pos;
- while (c != EOF) {
- if (c == '\n') {
- pos = 1;
- ++line;
- }
- if (char_in(c, may_skip)) {
- c = in.get();
- ++pos;
- } else if (char_in(c, chars)) {
- while(char_in(in.peek(), may_skip)) {
- if (in.peek() == '\n') {
- pos = 1;
- ++line;
- }
- in.get();
- ++pos;
- }
- in.putback(c);
- --pos;
- return;
- } else {
- throwJSONError(std::string("'") + c + "' read, one of \"" + chars + "\" expected", file, line, pos);
- }
- }
- throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
- }
- // TODO: Should we check for all other official escapes here (and
- // error on the rest)?
- std::string
- str_from_stringstream(std::istream &in, const std::string& file, const int line,
- int& pos) throw (JSONError)
- {
- char c = 0;
- std::stringstream ss;
- c = in.get();
- ++pos;
- if (c == '"') {
- c = in.get();
- ++pos;
- } else {
- throwJSONError("String expected", file, line, pos);
- }
- while (c != EOF && c != '"') {
- ss << c;
- if (c == '\\' && in.peek() == '"') {
- ss << in.get();
- ++pos;
- }
- c = in.get();
- ++pos;
- }
- return (ss.str());
- }
- std::string
- word_from_stringstream(std::istream &in, int& pos) {
- std::stringstream ss;
- while (isalpha(in.peek())) {
- ss << (char) in.get();
- }
- pos += ss.str().size();
- return (ss.str());
- }
- static std::string
- number_from_stringstream(std::istream &in, int& pos) {
- std::stringstream ss;
- while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
- in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
- ss << (char) in.get();
- }
- pos += ss.str().size();
- return (ss.str());
- }
- // Should we change from IntElement and DoubleElement to NumberElement
- // that can also hold an e value? (and have specific getters if the
- // value is larger than an int can handle)
- ElementPtr
- from_stringstream_number(std::istream &in, int &pos) {
- long int i = 0;
- double d = 0.0;
- bool is_double = false;
- char *endptr;
- std::string number = number_from_stringstream(in, pos);
- i = strtol(number.c_str(), &endptr, 10);
- if (*endptr != '\0') {
- d = strtod(number.c_str(), &endptr);
- is_double = true;
- if (*endptr != '\0') {
- isc_throw(JSONError, std::string("Bad number: ") + number);
- } else {
- if (d == HUGE_VAL || d == -HUGE_VAL) {
- isc_throw(JSONError, std::string("Number overflow: ") + number);
- }
- }
- } else {
- if (i == LONG_MAX || i == LONG_MIN) {
- isc_throw(JSONError, std::string("Number overflow: ") + number);
- }
- }
-
- if (is_double) {
- return (Element::create(d));
- } else {
- return (Element::create(i));
- }
- }
- ElementPtr
- from_stringstream_bool(std::istream &in, const std::string& file,
- const int line, int& pos)
- {
- const std::string word = word_from_stringstream(in, pos);
- if (boost::iequals(word, "True")) {
- return (Element::create(true));
- } else if (boost::iequals(word, "False")) {
- return (Element::create(false));
- } else {
- throwJSONError(std::string("Bad boolean value: ") + word, file, line, pos);
- // above is a throw shortcurt, return empty is never reached
- return (ElementPtr());
- }
- }
- ElementPtr
- from_stringstream_null(std::istream &in, const std::string& file,
- const int line, int& pos)
- {
- const std::string word = word_from_stringstream(in, pos);
- if (boost::iequals(word, "null")) {
- return (Element::create());
- } else {
- throwJSONError(std::string("Bad null value: ") + word, file, line, pos);
- return (ElementPtr());
- }
- }
- ElementPtr
- from_stringstream_string(std::istream& in, const std::string& file, int& line, int& pos)
- {
- return (Element::create(str_from_stringstream(in, file, line, pos)));
- }
- ElementPtr
- from_stringstream_list(std::istream &in, const std::string& file, int& line, int& pos)
- {
- char c = 0;
- ElementPtr list = Element::createList();
- ElementPtr cur_list_element;
- skip_chars(in, " \t\n", line, pos);
- while (c != EOF && c != ']') {
- if (in.peek() != ']') {
- cur_list_element = Element::fromJSON(in, file, line, pos);
- list->add(cur_list_element);
- skip_to(in, file, line, pos, ",]", " \t\n");
- }
- c = in.get();
- pos++;
- }
- return (list);
- }
- ElementPtr
- from_stringstream_map(std::istream &in, const std::string& file, int& line,
- int& pos)
- {
- ElementPtr map = Element::createMap();
- skip_chars(in, " \t\n", line, pos);
- char c = in.peek();
- if (c == '}') {
- // empty map, skip closing curly
- c = in.get();
- } else {
- while (c != EOF && c != '}') {
- std::string key = str_from_stringstream(in, file, line, pos);
- skip_to(in, file, line, pos, ":", " \t\n");
- // skip the :
- in.get();
- pos++;
- ElementPtr value = Element::fromJSON(in, file, line, pos);
- map->set(key, value);
-
- skip_to(in, file, line, pos, ",}", " \t\n");
- c = in.get();
- pos++;
- }
- }
- return (map);
- }
- }
- std::string
- Element::typeToName(Element::types type)
- {
- switch (type) {
- case Element::integer:
- return (std::string("integer"));
- case Element::real:
- return (std::string("real"));
- case Element::boolean:
- return (std::string("boolean"));
- case Element::string:
- return (std::string("string"));
- case Element::list:
- return (std::string("list"));
- case Element::map:
- return (std::string("map"));
- case Element::null:
- return (std::string("null"));
- case Element::any:
- return (std::string("any"));
- default:
- return (std::string("unknown"));
- }
- }
- Element::types
- Element::nameToType(const std::string& type_name) {
- if (type_name == "integer") {
- return (Element::integer);
- } else if (type_name == "real") {
- return (Element::real);
- } else if (type_name == "boolean") {
- return (Element::boolean);
- } else if (type_name == "string") {
- return (Element::string);
- } else if (type_name == "list") {
- return (Element::list);
- } else if (type_name == "map") {
- return (Element::map);
- } else if (type_name == "null") {
- return (Element::null);
- } else if (type_name == "any") {
- return (Element::any);
- } else {
- isc_throw(TypeError, type_name + " is not a valid type name");
- }
- }
- ElementPtr
- Element::fromJSON(std::istream& in) throw(JSONError) {
- int line = 1, pos = 1;
- return (fromJSON(in, "<istream>", line, pos));
- }
- ElementPtr
- Element::fromJSON(std::istream& in, const std::string& file_name) throw(JSONError)
- {
- int line = 1, pos = 1;
- return (fromJSON(in, file_name, line, pos));
- }
- ElementPtr
- Element::fromJSON(std::istream &in, const std::string& file, int& line, int& pos) throw(JSONError)
- {
- char c = 0;
- ElementPtr element;
- bool el_read = false;
- skip_chars(in, " \n\t", line, pos);
- while (c != EOF && !el_read) {
- c = in.get();
- pos++;
- switch(c) {
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- case '0':
- case '-':
- case '+':
- case '.':
- in.putback(c);
- element = from_stringstream_number(in, pos);
- el_read = true;
- break;
- case 't':
- case 'T':
- case 'f':
- case 'F':
- in.putback(c);
- element = from_stringstream_bool(in, file, line, pos);
- el_read = true;
- break;
- case 'n':
- case 'N':
- in.putback(c);
- element = from_stringstream_null(in, file, line, pos);
- el_read = true;
- break;
- case '"':
- in.putback('"');
- element = from_stringstream_string(in, file, line, pos);
- el_read = true;
- break;
- case '[':
- element = from_stringstream_list(in, file, line, pos);
- el_read = true;
- break;
- case '{':
- element = from_stringstream_map(in, file, line, pos);
- el_read = true;
- break;
- case EOF:
- break;
- default:
- throwJSONError(std::string("error: unexpected character ") + c, file, line, pos);
- break;
- }
- }
- if (el_read) {
- return (element);
- } else {
- isc_throw(JSONError, "nothing read");
- }
- }
- ElementPtr
- Element::fromJSON(const std::string &in) {
- std::stringstream ss;
- ss << in;
- return (fromJSON(ss, "<string>"));
- }
- // to JSON format
- void
- IntElement::toJSON(std::ostream& ss)
- {
- ss << intValue();
- }
- void
- DoubleElement::toJSON(std::ostream& ss)
- {
- ss << doubleValue();
- }
- void
- BoolElement::toJSON(std::ostream& ss)
- {
- if (boolValue()) {
- ss << "true";
- } else {
- ss << "false";
- }
- }
- void
- NullElement::toJSON(std::ostream& ss)
- {
- ss << "null";
- }
- void
- StringElement::toJSON(std::ostream& ss)
- {
- ss << "\"";
- ss << stringValue();
- ss << "\"";
- }
- void
- ListElement::toJSON(std::ostream& ss)
- {
- ss << "[ ";
- const std::vector<ElementPtr>& v = listValue();
- for (std::vector<ElementPtr>::const_iterator it = v.begin();
- it != v.end(); ++it) {
- if (it != v.begin()) {
- ss << ", ";
- }
- (*it)->toJSON(ss);
- }
- ss << " ]";
- }
- void
- MapElement::toJSON(std::ostream& ss)
- {
- ss << "{ ";
- const std::map<std::string, ElementPtr>& m = mapValue();
- for (std::map<std::string, ElementPtr>::const_iterator it = m.begin();
- it != m.end(); ++it) {
- if (it != m.begin()) {
- ss << ", ";
- }
- ss << "\"" << (*it).first << "\": ";
- if ((*it).second) {
- (*it).second->toJSON(ss);
- } else {
- ss << "None";
- }
- }
- ss << " }";
- }
- // throws when one of the types in the path (except the one
- // we're looking for) is not a MapElement
- // returns 0 if it could simply not be found
- // should that also be an exception?
- ElementPtr
- MapElement::find(const std::string& id) {
- const size_t sep = id.find('/');
- if (sep == std::string::npos) {
- return (get(id));
- } else {
- ElementPtr ce = get(id.substr(0, sep));
- if (ce) {
- // ignore trailing slash
- if (sep + 1 != id.size()) {
- return (ce->find(id.substr(sep + 1)));
- } else {
- return (ce);
- }
- } else {
- return (ElementPtr());
- }
- }
- }
- ElementPtr
- Element::fromWire(const std::string& s) {
- std::stringstream ss;
- ss << s;
- int line = 0, pos = 0;
- return (fromJSON(ss, "<wire>", line, pos));
- }
- ElementPtr
- Element::fromWire(std::stringstream& in, int length) {
- //
- // Check protocol version
- //
- //for (int i = 0 ; i < 4 ; ++i) {
- // const unsigned char version_byte = get_byte(in);
- // if (PROTOCOL_VERSION[i] != version_byte) {
- // throw DecodeError("Protocol version incorrect");
- // }
- //}
- //length -= 4;
- int line = 0, pos = 0;
- return (fromJSON(in, "<wire>", line, pos));
- }
- void
- MapElement::set(const std::string& key, ElementPtr value) {
- m[key] = value;
- }
- bool
- MapElement::find(const std::string& id, ElementPtr& t) {
- try {
- ElementPtr p = find(id);
- if (p) {
- t = p;
- return (true);
- }
- } catch (const TypeError& e) {
- // ignore
- }
- return (false);
- }
- bool
- IntElement::equals(ElementPtr other) {
- return (other->getType() == Element::integer) &&
- (i == other->intValue());
- }
- bool
- DoubleElement::equals(ElementPtr other) {
- return (other->getType() == Element::real) &&
- (d == other->doubleValue());
- }
- bool
- BoolElement::equals(ElementPtr other) {
- return (other->getType() == Element::boolean) &&
- (b == other->boolValue());
- }
- bool
- NullElement::equals(ElementPtr other) {
- return (other->getType() == Element::null);
- }
- bool
- StringElement::equals(ElementPtr other) {
- return (other->getType() == Element::string) &&
- (s == other->stringValue());
- }
- bool
- ListElement::equals(ElementPtr other) {
- if (other->getType() == Element::list) {
- const int s = size();
- if (s != other->size()) {
- return (false);
- }
- for (int i = 0; i < s; ++i) {
- if (!get(i)->equals(other->get(i))) {
- return (false);
- }
- }
- return (true);
- } else {
- return (false);
- }
- }
- bool
- MapElement::equals(ElementPtr other) {
- if (other->getType() == Element::map) {
- std::map<std::string, ElementPtr> m = mapValue();
- for (std::map<std::string, ElementPtr>::const_iterator it = m.begin();
- it != m.end() ; ++it) {
- if (other->contains((*it).first)) {
- if (!get((*it).first)->equals(other->get((*it).first))) {
- return (false);
- }
- } else {
- return (false);
- }
- }
- // quickly walk through the other map too, to see if there's
- // anything in there that we don't have. We don't need to
- // compare those elements; if one of them is missing we
- // differ (and if it's not missing the loop above has checked
- // it)
- m = other->mapValue();
- for (std::map<std::string, ElementPtr>::const_iterator it = m.begin();
- it != m.end() ; ++it) {
- if (!contains((*it).first)) {
- return (false);
- }
- }
- return (true);
- } else {
- return (false);
- }
- }
- bool
- isNull(ElementPtr p) {
- return (!p);
- }
- void
- removeIdentical(ElementPtr a, const ElementPtr b) {
- if (!b) {
- return;
- }
- if (a->getType() != Element::map || b->getType() != Element::map) {
- isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
- }
- std::map<std::string, ElementPtr> m = a->mapValue();
- for (std::map<std::string, ElementPtr>::const_iterator it = m.begin();
- it != m.end() ; ++it) {
- if (b->contains((*it).first)) {
- if (a->get((*it).first)->equals(b->get((*it).first))) {
- a->remove((*it).first);
- }
- }
- }
- }
- void
- merge(ElementPtr element, const ElementPtr other) {
- if (element->getType() != Element::map ||
- other->getType() != Element::map) {
- isc_throw(TypeError, "merge arguments not MapElements");
- }
-
- std::map<std::string, ElementPtr> m = other->mapValue();
- for (std::map<std::string, ElementPtr>::const_iterator it = m.begin();
- it != m.end() ; ++it) {
- if ((*it).second && (*it).second->getType() != Element::null) {
- element->set((*it).first, (*it).second);
- } else if (element->contains((*it).first)) {
- element->remove((*it).first);
- }
- }
- }
- }
- }
|