/* Copyright 2008,2009,2010 Edwin Eefting (edwin@datux.nl) This file is part of Synapse. Synapse is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Synapse is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Synapse. If not, see . */ #include "cvar.h" #include #include #include "clog.h" #include #include "exception/cexception.h" namespace synapse { /// COMMON STUFF Cvar::Cvar() { clear(); } Cvar::~Cvar() { } void Cvar::clear() { value=(void *)NULL; } bool Cvar::isEmpty() { return (value.which()==CVAR_EMPTY); } int Cvar::which() { return (value.which()); } string Cvar::getPrint(string prefix) { stringstream print; switch (value.which()) { case CVAR_EMPTY: print << "(empty)"; break; case CVAR_LONG_DOUBLE: print << (long double)(*this) << " (long double)"; break; case CVAR_STRING: print << (string)(*this) << " (string)"; break; case CVAR_MAP: //show all keys of the map, and show the values recursively if (begin()!=end()) { print << "(map:)"; for (CvarMap::iterator varI=begin(); varI!=end(); varI++) { print << endl; print << prefix << varI->first << " = "; print << varI->second.getPrint(prefix+" |"); } } else { print << "(empty map)"; } break; case CVAR_LIST: //show all items of the list recursively if (list().begin()!=list().end()) { print << "(list:)"; for (CvarList::iterator varI=list().begin(); varI!=list().end(); varI++) { print << endl; print << prefix << varI->getPrint(prefix+" |"); } } else { print << "(empty list)"; } break; default: print << "? (unknown type)"; break; } return print.str(); } bool Cvar::operator==( Cvar & other) { //wrong type if (value.which()!=other.value.which()) return (false); switch (value.which()) { case CVAR_EMPTY: return (true); break; case CVAR_LONG_DOUBLE: return ((long double)(*this)==(long double)other); break; case CVAR_STRING: return ((string)(*this)==(string)(other)); break; case CVAR_MAP: //wrong count? if (((CvarMap)(*this)).size()!=((CvarMap)other).size()) return (false); //recursively compare keys and values for (CvarMap::iterator varI=begin(); varI!=end(); varI++) { //key doesnt exists in other? if (!other.isSet(varI->first)) return (false); //compare values (this recurses) if (varI->second!=other[varI->first]) return (false); } return (true); break; case CVAR_LIST: //wrong count? if (list().size()!=other.list().size()) return (false); { //recursively compare values (order has to be the same as well) CvarList::iterator otherVarI=other.list().begin(); for (CvarList::iterator varI=list().begin(); varI!=list().end(); varI++) { //compare values (this recurses) if ((*varI)!=(*otherVarI)) return false; otherVarI++; } } return (true); break; default: castError("trying to compare unknown types?"); return (false); break; } } bool Cvar::operator!=( Cvar & other) { return !(*this == other); } /// CVAR_STRING stuff Cvar::Cvar(const string & value) { this->value=value; } // Cvar::Cvar(const char * value) // { // this->value=string(value); // } string & Cvar::operator=(const string & value) { this->value=value; return((string &)(*this)); } void Cvar::castError(const char * txt) { string s(txt); s+=": "+getPrint(); throw(synapse::runtime_error(s.c_str())); } Cvar::operator string & () { if (value.which()==CVAR_LONG_DOUBLE) { try { //DEB("Cvar " << this << ": converting CVAR_LONG_DOUBLE to CVAR_STRING"); value=lexical_cast(get(value)); } catch(...) { castError("casting error while converting to CVAR_STRING"); } } else if (value.which()==CVAR_STRING) { //native type, dont need to do anything } else if (value.which()==CVAR_EMPTY) { //change it from really empty, to an empty string //DEB("Cvar " << this << ": converting CVAR_EMPTY to CVAR_STRING"); value=""; } else { castError("cannot convert this to CVAR_STRING"); } return (get(value)); } string & Cvar::str() { return((string &)(*this)); } bool Cvar::operator==(const string & other) { return ((string)(*this)==(other)); } bool Cvar::operator!=(const string & other) { return ((string)(*this)!=(other)); } //bool Cvar::operator==(const char * other) //{ // return ((string)(*this)==other); //} // //bool Cvar::operator!=(const char * other) //{ // return ((string)(*this)!=other); //} /// CVAR_LONG_DOUBLE stuff long double & Cvar::operator=(const long double & value) { this->value=value; return((long double &)(*this)); } Cvar::Cvar(const long double & value) { this->value=value; } Cvar::operator long double &() { if (value.which()==CVAR_LONG_DOUBLE) { //native type, dont change anything } else if (value.which()==CVAR_STRING) { try { //convert string to long double //DEB("Cvar " << this << ": converting CVAR_STRING to CVAR_LONG_DOUBLE"); value=lexical_cast(get(value)); } catch(...) { castError("casting error while converting to CVAR_LONG_DOUBLE"); } } else if (value.which()==CVAR_EMPTY) { //change it from empty to 0 //DEB("Cvar " << this << ": converting CVAR_EMPTY to CVAR_LONG_DOUBLE"); value=0; } else { castError("cannot convert this to CVAR_LONG_DOUBLE"); } return (get(value)); } //bool Cvar::operator==(long double & other) //{ // return ((long double)(*this)==other); // //} /// CVAR_MAP stuff Cvar::operator CvarMap & () { if (value.which()==CVAR_EMPTY) { //DEB("Cvar " << this << ": converting CVAR_EMPTY to CVAR_MAP"); value=CvarMap(); } else if (value.which()!=CVAR_MAP) { castError("cannot convert this to CVAR_MAP"); } return (get(value)); } CvarMap & Cvar::map() { return ((CvarMap &)(*this)); } Cvar & Cvar::operator[] (const string & key) { return (((CvarMap&)(*this))[key]); } CvarMap::iterator Cvar::begin() { return ((CvarMap &)(*this)).begin(); } CvarMap::iterator Cvar::end() { return ((CvarMap &)(*this)).end(); } bool Cvar::isSet(const char * key) { return( ((CvarMap&)(*this)).find(key)!=((CvarMap&)(*this)).end() ); } bool Cvar::isSet(const string & key) { return( ((CvarMap&)(*this)).find(key)!=((CvarMap&)(*this)).end() ); } void Cvar::erase(const char * key) { CvarMap::iterator I=((CvarMap&)(*this)).find(key); if (I!=((CvarMap&)(*this)).end()) ((CvarMap&)(*this)).erase(I); } /// CVAR_LIST stuff Cvar::operator CvarList & () { if (value.which()==CVAR_EMPTY) { value=CvarList(); } else if (value.which()!=CVAR_LIST) { castError("cannot convert this to CVAR_LIST"); } return (get(value)); } CvarList & Cvar::list() { return ((CvarList &)(*this)); } /// JSON stuff /** Recursively converts a json_spirit Value to this Cvar */ void Cvar::fromJsonSpirit(Value &spiritValue) { switch(spiritValue.type()) { case(null_type): clear(); break; case(str_type): value=spiritValue.get_str(); break; case(real_type): value=spiritValue.get_real(); break; case(int_type): value=spiritValue.get_int(); break; case(bool_type): value=spiritValue.get_bool(); break; case(obj_type): value=CvarMap(); //convert the Object(string,Value) pairs to a CvarMap for (Object::iterator ObjectI=spiritValue.get_obj().begin(); ObjectI!=spiritValue.get_obj().end(); ObjectI++) { //recurse to convert the json_spirit pair into a CvarMap key->item. map()[ObjectI->name_].fromJsonSpirit(ObjectI->value_); } break; case(array_type): value=CvarList(); //convert the Array items to CvarList for (Array::iterator ArrayI=spiritValue.get_array().begin(); ArrayI!=spiritValue.get_array().end(); ArrayI++) { //recurse to convert the json_spirit value item into a CvarList item Cvar v; v.fromJsonSpirit(*ArrayI); list().push_back(v); } break; default: DEB("Unknown jsonspirit type: " << spiritValue.type()); castError("Cant convert this json spirit data to Cvar?"); break; } } /** Recursively converts this Cvar to a json_spirit Value. */ void Cvar::toJsonSpirit(Value &spiritValue) { int i; double d; switch(value.which()) { case(CVAR_EMPTY): spiritValue=Value(); break; case(CVAR_STRING): spiritValue=get(value); break; case(CVAR_LONG_DOUBLE): //work around to prevent numbers like 1.00000000000: i=(get(value)); d=(get(value)); if (d==i) spiritValue=i; else spiritValue=d; break; case(CVAR_MAP): //convert the CvarMap to a json_spirit Object with (String,Value) pairs spiritValue=Object(); for (CvarMap::iterator varI=begin(); varI!=end(); varI++) { Value subValue; //recurse to convert the map-value of the CvarMap into a json_spirit Value: varI->second.toJsonSpirit(subValue); //push the resulting Value onto the json_spirit Object spiritValue.get_obj().push_back(Pair( varI->first, subValue )); } break; case(CVAR_LIST): //convert the CvarList to a json_spirit Array with (String,Value) pairs spiritValue=Array(); for (CvarList::iterator varI=list().begin(); varI!=list().end(); varI++) { Value subValue; //recurse to convert the list item of the CvarList into a json_spirit Value: varI->toJsonSpirit(subValue); //push the resulting Value onto the json_spirit Object spiritValue.get_array().push_back(subValue); } break; default: castError("Cant convert this Cvar type to json spirit?"); break; } } /** converts this Cvar recursively into a json string */ void Cvar::toJson(string & jsonStr) { Value spiritValue; toJsonSpirit(spiritValue); jsonStr=write(spiritValue); } //"better" version of json read that throws nicer exceptions void Cvar::readJsonSpirit(const string & jsonStr, Value & spiritValue) { //TODO:how safe is it actually to let json_spirit parse untrusted input? (regarding DoS, buffer overflows, etc) if (!json_spirit::read(jsonStr, spiritValue)) { //this function is 3 times slower, but gives a nice exception with an error message try { json_spirit::read_or_throw(jsonStr, spiritValue); } catch(json_spirit::Error_position e) { //reformat the exception to something generic: stringstream s; s << "JSON parse error at line " << e.line_<< ", column " << e.column_ << ": " << e.reason_; throw(synapse::runtime_error(s.str())); } } } void Cvar::toJsonFormatted(string & jsonStr) { Value spiritValue; toJsonSpirit(spiritValue); jsonStr=write_formatted(spiritValue); } void Cvar::fromJson(string & jsonStr) { //parse json input Value spiritValue; readJsonSpirit(jsonStr,spiritValue); fromJsonSpirit(spiritValue); } //test functionality of Cvar class, returns false on failure. //Its wise to also run this small and fast test in the non-debug version, to detect strange problems with compiler optimizations or different compiler version. bool Cvar::selfTest() { #define TEST(condition) if (!(condition)) return (false) //tests if operation throws an exception. //NOTE: dont forget to check if the value is still what it is supposed to be, AFTER the throw. #define TESTTHROW(operation) \ { \ bool ok=false; \ try \ { \ operation ; \ } \ catch (const synapse::runtime_error& e) \ { \ ok=true; \ DEB("exception: " << e.what()); \ } \ catch(...) \ { \ ok=true; \ } \ if (!ok) ERROR("did not throw exception!"); \ TEST(ok); \ } { DEB("test: Selftesting CVAR_LONG_DOUBLE"); DEB("test: empty long value"); Cvar e; TEST((int)e==0); DEB("test: use long as boolean"); e=1; TEST(e); DEB("test: long construct"); Cvar a(2.12); TEST(a==2.12); DEB("test: long assignment"); TEST((a=3.13) == 3.13); TEST(a==3.13); DEB("test: long assignment reference") Cvar b; TEST((a=b=5.15) == 5.15); TEST(a==5.15); TEST(b==5.15); DEB("test: int to string"); a=6; TEST((string)a=="6"); DEB("test: long to string"); a=6.14; TEST((string)a=="6.13999999999999968026"); TEST(a.str()=="6.13999999999999968026"); DEB("test: long to map"); a=7; TESTTHROW((CvarMap)a); TESTTHROW(a.map()); TEST(a==7); DEB("test: long to list"); a=8; TESTTHROW(a.list()); TEST(a==8); DEB("test long compare"); Cvar c; c=a; TEST(a==c); DEB("test: long re-assign string"); a=9; TEST(a==9); a="test"; TEST(a.str()=="test"); DEB("test: long re-assign list"); a=9; TEST(a==9); Cvar l; l.list(); a=l; TEST(a.list().size()==0); DEB("test: long re-assign map"); a=9; TEST(a==9); Cvar m; m.map(); a=m; TEST(a.map().size()==0); DEB("test: basic arithmetic"); a=5; a=a+1; a=a-2; TEST(a==4); } { DEB("test: Selftesting CVAR_STRING"); DEB("test: empty string value"); Cvar e; TEST(e==""); DEB("test: use string as boolean"); e="1"; TEST(e); TEST(e=="1"); DEB("test: use invalid string as boolean"); e="blah"; TESTTHROW(if (e);); TEST(e=="blah"); DEB("test: string construct from const char"); Cvar a("first"); TEST(a=="first"); DEB("test: const char assignment"); TEST((a="second") == "second"); TEST(a=="second"); DEB("test: const char assignment reference") Cvar b; TEST((a=b="third") == "third"); TEST(a=="third"); TEST(b=="third"); DEB("test: valid string number to int"); a="6"; TEST(a==6); DEB("test: invalid string number to int"); a="blah"; TESTTHROW(a=a+6); TEST(a=="blah"); DEB("test: string to map"); a="fourth"; TESTTHROW((CvarMap)a); TESTTHROW(a.map()); TEST(a=="fourth"); DEB("test: string to list"); a="fifth"; CvarList d; TESTTHROW(d=a); TESTTHROW(a.list()); TEST(a=="fifth"); DEB("test string compare"); Cvar c; c=a; TEST(a==c); DEB("test: string re-assign long"); a="sixth"; TEST(a=="sixth"); a=7; TEST(a==7); DEB("test: string re-assign list"); a="sixth"; TEST(a=="sixth"); Cvar l; l.list(); a=l; TEST(a.list().size()==0); DEB("test: string re-assign map"); a="sixth"; TEST(a=="sixth"); Cvar m; m.map(); a=m; TEST(a.map().size()==0); } { DEB("test: Selftesting CVAR_LIST"); DEB("test: create basic list"); Cvar a; a.list().push_back(Cvar("one")); a.list().push_back(2); TEST(a.list().front()=="one"); TEST(a.list().back()==2); DEB("test: list to long"); TESTTHROW(a=a+1); TEST(a.list().back()==2); DEB("test: list to string"); TESTTHROW(a.str()); TEST(a.list().back()==2); DEB("test: list to map"); TESTTHROW(a.map()); TEST(a.list().back()==2); DEB("test list compare"); Cvar c; c=a; TEST(a==c); DEB("test: list re-assign long"); a.clear(); a.list(); a=5; TEST(a==5); DEB("test: list re-assign string"); a.clear(); a.list(); a="test"; TEST(a=="test"); DEB("test: list re-assign map"); a.clear(); a.list(); Cvar b; b.map(); a=b; TEST(a.map().size()==0); } { DEB("test: Selftesting CVAR_MAP"); DEB("test: create basic map"); Cvar a; a["first"]=1; a["second"]="two"; TEST(a["first"]==1); TEST(a["second"]=="two"); DEB("test: map to long"); TESTTHROW(a=a+1); TEST(a["first"]==1); DEB("test: map to string"); TESTTHROW(a.str()); TEST(a["first"]==1); DEB("test: map to list"); TESTTHROW(a.list()); TEST(a["first"]==1); DEB("test map compare"); Cvar c; c=a; TEST(a==c); DEB("test: map re-assign long"); a.clear(); a.map(); a=5; TEST(a==5); DEB("test: map re-assign string"); a.clear(); a.map(); a="test"; TEST(a=="test"); DEB("test: map re-assign list"); a.clear(); a.map(); Cvar b; b.list(); a=b; TEST(a.list().size()==0); } { DEB("test: Selftesting Cvar complex data structures"); DEB("test: create complex structure"); Cvar a; a["empty"]; a["number"]=1; a["list"].list().push_back(1); a["list"].list().push_back(Cvar()); a["string"]="text"; a["map"]["number2"]=2; a["map"]["list2"].list().push_back(2); a["map"]["string2"]="text2"; a["list"].list().back()["submap"].list().push_back(3); DEB("test: complex map layout:" << a.getPrint()); DEB("test: convert to json and back"); string s; a.toJson(s); Cvar b; b.fromJson(s); DEB("test: complex compare json result"); TEST(a==b); //make a deep but small change: b["list"].list().back()["submap"].list().back()=4; TEST(a!=b); //change it back: b["list"].list().back()["submap"].list().back()=3; TEST(a==b); DEB("test: convert to formatted json and back"); a.toJsonFormatted(s); b.fromJson(s); TEST(a==b); } DEB("test: all Cvar tests OK"); return(true); } }