RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
soa/js/js_value.cc
00001 /* js_value.cc
00002    Jeremy Barnes, 21 July 2010
00003    Copyright (c) 2010 Datacratic.  All rights reserved.
00004 
00005    Javascript value handling.
00006 */
00007 
00008 #include "js_value.h"
00009 #include "js_utils.h"
00010 #include "jml/utils/string_functions.h"
00011 #include "jml/arch/demangle.h"
00012 #include "jml/arch/backtrace.h"
00013 #include "soa/types/date.h"
00014 #include "soa/types/string.h"
00015 #include <boost/lexical_cast.hpp>
00016 #include <boost/foreach.hpp>
00017 #include "soa/jsoncpp/json.h"
00018 #include "node/node_buffer.h"
00019 #include <cxxabi.h>
00020 using namespace std;
00021 using namespace ML;
00022 using namespace Datacratic::JS;
00023 
00024 namespace node {
00025 
00026 // Define as a weak symbol to avoid linker errors when linking without node
00027 __attribute__((__weak__))
00028 Buffer * Buffer::New(size_t) 
00029 {
00030     throw Exception("node needs to be linked in to use non-ASCII strings");
00031 }
00032 
00033 __attribute__((__weak__))
00034 bool Buffer::HasInstance(v8::Handle<v8::Value> val) 
00035 {
00036     return false;  // if node isn't linked in, then it can't be a buffer
00037 }
00038  
00039 } // namespace node
00040 
00041 namespace Datacratic {
00042 namespace JS {
00043 
00044 
00045 /*****************************************************************************/
00046 /* JSVALUE                                                                   */
00047 /*****************************************************************************/
00048 
00049 JSValue::operator v8::Handle<v8::Object>() const
00050 {
00051     return toObject(*this);
00052 }
00053 
00054 
00055 /*****************************************************************************/
00056 /* JSOBJECT                                                                  */
00057 /*****************************************************************************/
00058 
00059 void
00060 JSObject::
00061 initialize()
00062 {
00063     *this = v8::Object::New();
00064 }
00065 
00066 void
00067 JSObject::
00068 add(const std::string & key, const std::string & value)
00069 {
00070     (*this)->Set(v8::String::NewSymbol(key.c_str()),
00071                  v8::String::New(value.c_str(), value.length()));
00072 }
00073 
00074 void
00075 JSObject::
00076 add(const std::string & key, const JSValue & value)
00077 {
00078     (*this)->Set(v8::String::NewSymbol(key.c_str()),
00079                  value);
00080 }
00081 
00082 
00083 /*****************************************************************************/
00084 /* CONVERSIONS                                                               */
00085 /*****************************************************************************/
00086 
00087 void to_js(JSValue & jsval, signed int value)
00088 {
00089     jsval = v8::Integer::New(value);
00090 }
00091 
00092 void to_js(JSValue & jsval, unsigned int value)
00093 {
00094     jsval = v8::Integer::NewFromUnsigned(value);
00095 }
00096 
00097 void to_js(JSValue & jsval, signed long value)
00098 {
00099     if (value <= INT_MAX && value >= INT_MIN)
00100         jsval = v8::Integer::New(value);
00101     else jsval = v8::Number::New((double) value);
00102 }
00103 
00104 void to_js(JSValue & jsval, unsigned long value)
00105 {
00106     if (value <= UINT_MAX)
00107         jsval = v8::Integer::NewFromUnsigned(value);
00108     else jsval = v8::Number::New((double) value);
00109 }
00110 
00111 void to_js(JSValue & jsval, signed long long value)
00112 {
00113     if (value <= INT_MAX && value >= INT_MIN)
00114         jsval = v8::Integer::New(value);
00115     else jsval = v8::Number::New((double) value);
00116 }
00117 
00118 void to_js(JSValue & jsval, unsigned long long value)
00119 {
00120     if (value <= UINT_MAX)
00121         jsval = v8::Integer::NewFromUnsigned(value);
00122     else jsval = v8::Number::New((double) value);
00123 }
00124 
00125 void to_js(JSValue & jsval, float value)
00126 {
00127     jsval = v8::Number::New(value);
00128 }
00129 
00130 void to_js(JSValue & jsval, double value)
00131 {
00132     jsval = v8::Number::New(value);
00133 }
00134 
00135 void to_js_bool(JSValue & jsval, bool value)
00136 {
00137     jsval = v8::Boolean::New(value);
00138 }
00139 
00140 void to_js(JSValue & jsval, const std::string & value)
00141 {
00142     bool isAscii = true;
00143     for (unsigned i = 0;  i < value.size() && isAscii;  ++i)
00144         if (value[i] == 0 || value[i] > 127)
00145             isAscii = false;
00146     if (isAscii)
00147         jsval = v8::String::New(value.c_str(), value.length());
00148     else {
00149         // We can't represent this in ASCII.  In this case, we need to use a
00150         // buffer.
00151         node::Buffer * buffer
00152             = node::Buffer::New(value.size());
00153         std::copy(value.begin(), value.end(), node::Buffer::Data(buffer));
00154         jsval = buffer->handle_;
00155     }
00156 }
00157 
00158 void to_js(JSValue & jsval, const Utf8String & value)
00159 {
00160     jsval = v8::String::New(value.rawData(), value.rawLength());
00161 }
00162 
00163 void to_js(JSValue & jsval, const char * value)
00164 {
00165     jsval = v8::String::New(value);
00166 }
00167 
00168 void to_js(JSValue & jsval, const Json::Value & value)
00169 {
00170     switch(value.type())
00171     {
00172     case Json::objectValue:
00173     {
00174         v8::HandleScope scope;
00175         v8::Local<v8::Object> obj = v8::Object::New();
00176         BOOST_FOREACH(string key, value.getMemberNames())
00177         {
00178             JSValue member;
00179             to_js(member, value[key]);
00180             obj->Set(v8::String::NewSymbol(key.c_str()), member);
00181         }
00182         jsval = scope.Close(obj);
00183     }
00184         break;
00185     case Json::arrayValue:
00186     {
00187         v8::HandleScope scope;
00188         v8::Local<v8::Array> arr = v8::Array::New();
00189         for(int i=0;i< value.size(); ++i)
00190         {
00191             JSValue elem;
00192             to_js(elem, value[i]);
00193             arr->Set(i, elem);
00194         }
00195         jsval = scope.Close(arr);
00196     }
00197         break;
00198     case Json::realValue:
00199         to_js(jsval, value.asDouble());
00200         break;
00201     case Json::stringValue:
00202         to_js(jsval, value.asString());
00203         break;
00204     case Json::intValue:
00205         to_js(jsval, value.asInt());
00206         break;
00207     case Json::uintValue:
00208         to_js(jsval, value.asUInt());
00209         break;
00210     case Json::booleanValue:
00211         to_js(jsval, value.asBool());
00212         break;
00213     case Json::nullValue:
00214         jsval = v8::Null();
00215         break;
00216     default:
00217         throw ML::Exception("Can't convert from JsonCpp to JSValue");
00218         break;
00219     }
00220 }
00221 
00222 void to_js(JSValue & jsval, Date value)
00223 {
00224     jsval = v8::Date::New(value.secondsSinceEpoch() * 1000.0);
00225 }
00226 
00227 namespace {
00228 
00229 int64_t check_to_int2(const JSValue & val)
00230 {
00231     //cerr << "check_to_int " << cstr(val) << endl;
00232 
00233     int64_t ival = val->IntegerValue();
00234     double dval = val->NumberValue();
00235 
00236     //cerr << "  ival = " << ival << endl;
00237     //cerr << "  dval = " << dval << endl;
00238 
00239     if (ival != 0 && ival == dval) return ival;
00240 
00241     if (dval > std::numeric_limits<uint64_t>::max()
00242         || dval < std::numeric_limits<uint64_t>::min())
00243         throw ML::Exception("Cannot fit " + cstr(val) + " into an integer");
00244         
00245     v8::Local<v8::Number> num;
00246 
00247     if (val->IsArray())
00248         throw Exception("cannot convert array to integer");
00249 
00250     //bool debug = val->IsArray();
00251     //if (debug) cerr << "is array" << endl;
00252 
00253     if (val->IsNumber() || v8::Number::Cast(*val)) {
00254         //if (debug)
00255         //cerr << "is number" << endl;
00256         double d = val->NumberValue();
00257         if (!isfinite(d))
00258             throw Exception("cannot convert double value "
00259                             + cstr(val) + " to integer");
00260         return d;
00261     }
00262     if (val->IsString()) {
00263         //if (debug)
00264         //cerr << "is string" << endl;
00265 
00266         int64_t ival = val->IntegerValue();
00267         if (ival != 0) return ival;
00268         string s = lowercase(cstr(val));
00269         try {
00270             return boost::lexical_cast<int64_t>(s);
00271         } catch (const boost::bad_lexical_cast & error) {
00272             throw Exception("cannot convert string value \""
00273                             + cstr(val) + "\" (\"" + s + "\") to integer");
00274         }
00275     }
00276 
00277 #define try_type(x) if (val->x()) cerr << #x << endl;
00278 
00279     try_type(IsUndefined);
00280     try_type(IsNull);
00281     try_type(IsTrue);
00282     try_type(IsFalse);
00283     try_type(IsString);
00284     try_type(IsFunction);
00285     try_type(IsArray);
00286     try_type(IsObject);
00287     try_type(IsBoolean);
00288     try_type(IsNumber);
00289     try_type(IsExternal);
00290     try_type(IsInt32);
00291     try_type(IsDate);
00292 
00293     if (val->IsObject()) {
00294         cerr << "object: " << cstr(val->ToObject()->ObjectProtoToString())
00295              << endl;
00296         cerr << "val->NumberValue() = " << val->NumberValue() << endl;
00297     }
00298 
00299     backtrace();
00300 
00301     throw Exception("cannot convert value \""
00302                     + cstr(val) + "\" to integer");
00303 }
00304 
00305 template<typename T>
00306 T check_to_int(const JSValue & val)
00307 {
00308     if (val.IsEmpty())
00309         throw Exception("from_js: value is empty");
00310 
00311     
00312 
00313     int64_t result1 = check_to_int2(val);
00314     T result2 = result1;
00315     if (result1 != result2)
00316         throw Exception("value " + cstr(val) + " does not fit in type "
00317                         + ML::type_name<T>());
00318     return result2;
00319 }
00320 
00321 } // file scope
00322 
00323 signed int from_js(const JSValue & val, signed int *)
00324 {
00325     //cerr << "from_js signed int" << endl;
00326     return check_to_int<signed int>(val);
00327 }
00328 
00329 unsigned int from_js(const JSValue & val, unsigned *)
00330 {
00331     //cerr << "from_js unsigned int" << endl;
00332     return check_to_int<unsigned int>(val);
00333 }
00334 
00335 signed long from_js(const JSValue & val, signed long *)
00336 {
00337     //cerr << "from_js signed long" << endl;
00338     return check_to_int<signed long>(val);
00339 }
00340 
00341 unsigned long from_js(const JSValue & val, unsigned long *)
00342 {
00343     //cerr << "from_js unsigned long" << endl;
00344     return check_to_int<unsigned long>(val);
00345 }
00346 
00347 signed long long from_js(const JSValue & val, signed long long *)
00348 {
00349     //cerr << "from_js signed long" << endl;
00350     return check_to_int<signed long long>(val);
00351 }
00352 
00353 unsigned long long from_js(const JSValue & val, unsigned long long *)
00354 {
00355     //cerr << "from_js unsigned long" << endl;
00356     return check_to_int<unsigned long long>(val);
00357 }
00358 
00359 float from_js(const JSValue & val, float *)
00360 {
00361     //cerr << "from_js float" << endl;
00362     return from_js(val, (double *)0);
00363 }
00364 
00365 double from_js(const JSValue & val, double *)
00366 {
00367     //cerr << "from_js double" << endl;
00368     const double result = val->NumberValue();
00369     if (std::isnan(result)) {
00370         if (val->IsNumber()) return result;
00371         if (val->IsString()) {
00372             string s = lowercase(cstr(val));
00373             if (s == "nan" || s == "-nan")
00374                 return result;
00375             throw ML::Exception("string value \"%s\" is not converible to "
00376                                 "floating point",
00377                                 s.c_str());
00378         }
00379         throw Exception("value \"%s\" not convertible to floating point",
00380                         cstr(val).c_str());
00381     }
00382     return result;
00383 }
00384 
00385 bool from_js(const JSValue & val, bool *)
00386 {
00387     bool result = val->BooleanValue();
00388     return result;
00389 }
00390 
00391 std::string from_js(const JSValue & val, std::string *)
00392 {
00393     if (node::Buffer::HasInstance(val)) {
00394         //cerr << "from_js with buffer" << endl;
00395         return string(node::Buffer::Data(val), node::Buffer::Length(val));
00396     }
00397     else
00398     {
00399         return *v8::String::AsciiValue(val);
00400     }
00401 }
00402 
00403 Json::Value from_js(const JSValue & val, Json::Value *)
00404 {
00405     if (val.IsEmpty())
00406         throw ML::Exception("empty val");
00407 
00408     //cerr << cstr(val) << endl;
00409 
00410     if(val->IsObject())
00411     {
00412         if(v8::Date::Cast(*val)->IsDate())
00413         {
00414             return from_js(val, (Datacratic::Date*)(0)).secondsSinceEpoch();
00415         }
00416         if(val->IsArray())
00417         {
00418             Json::Value result (Json::arrayValue);
00419 
00420             auto arrPtr = v8::Array::Cast(*val);
00421             for(int i=0; i<arrPtr->Length(); ++i)
00422             {
00423                 result[i] = from_js(arrPtr->Get(i), (Json::Value *)0);
00424             }
00425 
00426             return result;
00427         }
00428         else
00429         {
00430             Json::Value result (Json::objectValue);
00431             auto objPtr = v8::Object::Cast(*val);
00432             v8::Handle<v8::Array> prop_names = objPtr->GetPropertyNames();
00433 
00434             for (unsigned i = 0;  i < prop_names->Length();  ++i)
00435             {
00436                 v8::Handle<v8::String> key
00437                     = prop_names->Get(v8::Uint32::New(i))->ToString();
00438                 if (!objPtr->HasOwnProperty(key)) continue;
00439                 result[from_js(key, (string *)0)] =
00440                         from_js(objPtr->Get(key), (Json::Value *)0);
00441             }
00442 
00443             return result;
00444         }
00445     }
00446     if(val->IsBoolean())
00447     {
00448         return from_js(val, (bool *)0);
00449     }
00450     if(val->IsString())
00451     {
00452         return from_js(val, (Utf8String *)0);
00453     }
00454     if(val->IsInt32())
00455     {
00456         return from_js(val, (int32_t *)0);
00457     }
00458     if(val->IsUint32())
00459     {
00460         return from_js(val, (uint32_t *)0);
00461     }
00462     if(val->IsNumber())
00463     {
00464         return from_js(val, (double *)0);
00465     }
00466     if (val->IsNull() || val->IsUndefined())
00467         return Json::Value();
00468     throw ML::Exception("can't convert from JSValue %s to Json::Value",
00469                         cstr(val).c_str());
00470 }
00471 
00472 Date from_js(const JSValue & val, Date *)
00473 {
00474     if(!v8::Date::Cast(*val)->IsDate())
00475         throw ML::Exception("Couldn't convert from " + cstr(val) + " to Datacratic::Date");
00476     return Date::fromSecondsSinceEpoch(v8::Date::Cast(*val)->NumberValue()
00477                                        / 1000.0);
00478 }
00479 
00480 Utf8String from_js(const JSValue & val, Utf8String *)
00481 {
00482     return Utf8String(*v8::String::Utf8Value(val)) ;
00483 }
00484 
00485 Json::Value from_js_ref(const JSValue & val, Json::Value *)
00486 {
00487     return from_js(val, (Json::Value *)0);
00488 }
00489 
00490 
00491 } // namespace JS
00492 } // namespace Datacratic
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator