RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
common/currency.cc
00001 /* currency.cc
00002    Jeremy Barnes, 5 November 2012
00003    Copyright (c) 2012 Datacratic Inc.  All rights reserved.
00004 
00005 */
00006 
00007 #include "rtbkit/common/currency.h"
00008 
00009 using namespace std;
00010 using namespace ML;
00011 using namespace Datacratic;
00012 
00013 
00014 namespace RTBKIT {
00015 
00016 
00017 /*****************************************************************************/
00018 /* CURRENCY CODE                                                             */
00019 /*****************************************************************************/
00020 
00021 std::string toString(CurrencyCode code)
00022 {
00023     if (code == CurrencyCode::CC_NONE)
00024         return "NONE";
00025     else if (code == CurrencyCode::CC_USD)
00026         return "USD";
00027     else throw ML::Exception("unknown currency code");
00028 }
00029 
00030 CurrencyCode parseCurrencyCode(const std::string & code)
00031 {
00032     if (code == "USD")
00033         return CurrencyCode::CC_USD;
00034     else if (code == "NONE")
00035         return CurrencyCode::CC_NONE;
00036     else throw ML::Exception("unknown currency code");
00037 }
00038 
00039 CurrencyCode jsonDecode(const Json::Value & j, CurrencyCode *)
00040 {
00041     if (j.isNull())
00042         return CurrencyCode::CC_NONE;
00043     string s = j.asString();
00044     if (s.size() > 4)
00045         throw ML::Exception("unknown currency code " + s);
00046     if (s == "USD")
00047         return CurrencyCode::CC_USD;
00048     
00049     throw ML::Exception("not done: decoding other currency " + s);
00050 }
00051 
00052 Json::Value jsonEncode(CurrencyCode code)
00053 {
00054     switch (code) {
00055     case CurrencyCode::CC_NONE:
00056         return Json::Value();
00057     case CurrencyCode::CC_USD:
00058         return Json::Value("USD");
00059     default:
00060         throw ML::Exception("not done: encoding other currencies");
00061     }
00062 }
00063 
00064 /*****************************************************************************/
00065 /* AMOUNT                                                                    */
00066 /*****************************************************************************/
00067 
00068 
00069 Json::Value
00070 Amount::
00071 toJson() const
00072 {
00073     if (isZero())
00074         return 0;
00075 
00076     Json::Value result(Json::arrayValue);
00077     result[0] = value;
00078     result[1] = getCurrencyStr();
00079 
00080     //cerr << "Amount toJson() gave " << result.toString() << endl;
00081 
00082     return result;
00083 }
00084 
00085 std::string
00086 Amount::
00087 getCurrencyStr(CurrencyCode currencyCode)
00088 {
00089     switch (currencyCode) {
00090     case CurrencyCode::CC_NONE: return "NONE";
00091     case CurrencyCode::CC_USD:  return "USD/1M";
00092     default:
00093         throw ML::Exception("unknown currency code %d", (uint32_t)currencyCode);
00094     }
00095 }
00096 
00097 std::string
00098 Amount::
00099 getCurrencyStr() const
00100 {
00101     return getCurrencyStr(currencyCode);
00102 }
00103 
00104 std::string
00105 Amount::
00106 toString() const
00107 {
00108     if (!*this)
00109         return "0";
00110     return to_string(value) + getCurrencyStr();
00111 }
00112 
00113 Amount
00114 Amount::
00115 fromJson(const Json::Value & val)
00116 {
00117     //cerr << "Amount fromJson " << val.toString() << endl;
00118 
00119     if (val.isArray()) {
00120         if (val.size() != 2)
00121             throw ML::Exception("invalid amount size");
00122         return Amount(val[1].asString(), val[0].asInt());
00123     }
00124     else if (val.isString())
00125         return parse(val.asString());
00126     else if (val.isNumeric() && val.asDouble() == 0.0)
00127         return Amount();
00128     else if (val.isNull())
00129         return Amount();
00130     else if (val.isObject()) {
00131         string currencyCode = "NONE";
00132         int64_t value = 0;
00133         for (auto it = val.begin(), end = val.end();  it != end;  ++it) {
00134             if (it.memberName() == "value")
00135                 value = it->asInt();
00136             else if (it.memberName() == "currencyCode")
00137                 currencyCode = it->asString();
00138             else throw ML::Exception("unknown Amount field " + it.memberName());
00139         }
00140         return Amount(currencyCode, value);
00141     }
00142     else throw ML::Exception("unknown amount " + val.toString());
00143 }
00144 
00145 Amount
00146 Amount::
00147 parse(const std::string & value)
00148 {
00149     if (value == "0")
00150         return Amount();
00151 
00152     const char * p = value.c_str();
00153     char * ep = 0;
00154     errno = 0;
00155     long long val = strtoll(p, &ep, 10);
00156     if (errno != 0)
00157         throw ML::Exception("invalid amount parsed");
00158     string currency(ep);
00159 
00160     if (currency == "") {
00161         throw ML::Exception("no currency on Amount " + value);
00162     }
00163 
00164     return Amount(currency, val);
00165 }
00166 
00167 CurrencyCode
00168 Amount::
00169 parseCurrency(const std::string & currency)
00170 {
00171     if (currency == "NONE")
00172         return CurrencyCode::CC_NONE;
00173     else if (currency == "USD/1M")
00174         return CurrencyCode::CC_USD;
00175     else throw ML::Exception("unknown currency code " + currency);
00176 }
00177 
00178 std::ostream &
00179 operator << (std::ostream & stream, Amount amount)
00180 {
00181     return stream << amount.toString();
00182 }
00183 
00184 void
00185 Amount::
00186 assertCurrencyIsCompatible(const Amount & other) const
00187 {
00188     if (!currencyIsCompatible(other))
00189         throw ML::Exception("currencies are not compatible: "
00190                             + toString() + " vs " + other.toString());
00191 }
00192 
00193 
00194 bool
00195 Amount::
00196 operator < (const Amount & other) const
00197 {
00198     assertCurrencyIsCompatible(other);
00199     return value < other.value;
00200 }
00201 
00202 bool
00203 Amount::
00204 operator <= (const Amount & other) const
00205 {
00206     assertCurrencyIsCompatible(other);
00207     return value <= other.value;
00208 }
00209 
00210 bool
00211 Amount::
00212 operator > (const Amount & other) const
00213 {
00214     assertCurrencyIsCompatible(other);
00215     return value > other.value;
00216 }
00217 
00218 bool
00219 Amount::
00220 operator >= (const Amount & other) const
00221 {
00222     assertCurrencyIsCompatible(other);
00223     return value >= other.value;
00224 }
00225 
00226 void
00227 Amount::
00228 serialize(ML::DB::Store_Writer & store) const
00229 {
00230     if (!*this) {
00231         store << (unsigned char)0;
00232         return;
00233     }
00234     
00235     store << (unsigned char)(1 + (value < 0))
00236           << (uint32_t)currencyCode
00237           << ML::DB::compact_size_t(value >= 0 ? value : -value); // need compact_int_t as it is signed
00238 }
00239 
00240 void
00241 Amount::
00242 reconstitute(ML::DB::Store_Reader & store)
00243 {
00244     unsigned char version;
00245     store >> version;
00246     if (version == 0) {
00247         *this = Amount();
00248         return;
00249     }
00250 
00251     if (version > 2)
00252         throw ML::Exception("invalid version reconstituting Amount");
00253 
00254     uint32_t ccode;
00255     store >> ccode;
00256     currencyCode = (CurrencyCode)ccode;
00257     value = ML::DB::compact_size_t(store);
00258     if (version == 2)
00259         value = -value;
00260 
00261     //cerr << "reconstituted = " << *this << endl;
00262 }
00263 
00264 
00265 /*****************************************************************************/
00266 /* CURRENCY POOL                                                             */
00267 /*****************************************************************************/
00268 
00269 bool
00270 CurrencyPool::
00271 operator == (const CurrencyPool & other) const
00272 {
00273     auto checkContains = [] (const CurrencyPool & pool1,
00274                              const CurrencyPool & pool2)
00275         {
00276             for (auto amt: pool1.currencyAmounts) {
00277                 if (pool2.getAvailable(amt.currencyCode) != amt)
00278                     return false;
00279             }
00280 
00281             return true;
00282         };
00283 
00284     return checkContains(*this, other) && checkContains(other, *this);
00285 }
00286 
00287 Amount
00288 CurrencyPool::
00289 getAvailable(const CurrencyCode & currency) const
00290 {
00291     for (auto & am: currencyAmounts) {
00292         if (am.currencyCode == currency) {
00293             return am;
00294         }
00295     }
00296 
00297     return Amount(currency, 0);
00298 }
00299 
00300 bool
00301 CurrencyPool::
00302 hasAvailable(const Amount & amount) const
00303 {
00304     return getAvailable(amount.currencyCode).value >= amount.value;
00305 }
00306 
00307 Json::Value
00308 CurrencyPool::
00309 toJson() const
00310 {
00311     Json::Value result(Json::objectValue);
00312     for (auto a: currencyAmounts)
00313         result[a.getCurrencyStr()] = a.value;
00314     return result;
00315 }
00316 
00317 CurrencyPool
00318 CurrencyPool::
00319 fromJson(const Json::Value & json)
00320 {
00321     CurrencyPool result;
00322 
00323     for (auto it = json.begin(), end = json.end();  it != end;  ++it)
00324         result += Amount(it.memberName(), (*it).asInt());
00325 
00326     //cerr << "getting currencyPool from JSON " << json
00327     //     << " returned " << result << endl;
00328 
00329     return result;
00330 }
00331 
00332 std::string
00333 CurrencyPool::
00334 toString() const
00335 {
00336     if (isZero())
00337         return "0";
00338 
00339     string result;
00340     for (unsigned i = 0;  i < currencyAmounts.size();  ++i) {
00341         if (i > 0)
00342             result += ", ";
00343         result += currencyAmounts[i].toString();
00344     }
00345     return result;
00346 }
00347 
00348 std::ostream & operator << (std::ostream & stream, CurrencyPool pool)
00349 {
00350     return stream << pool.toString();
00351 }
00352 
00353 void
00354 CurrencyPool::
00355 serialize(ML::DB::Store_Writer & store) const
00356 {
00357     store << (unsigned char)0
00358           << currencyAmounts;
00359 }
00360 
00361 void
00362 CurrencyPool::
00363 reconstitute(ML::DB::Store_Reader & store)
00364 {
00365     unsigned char version;
00366     store >> version;
00367     if (version != 0)
00368         throw ML::Exception("error reconstituting currency pool");
00369     store >> currencyAmounts;
00370 }
00371 
00372 bool
00373 CurrencyPool::
00374 isSameOrPastVersion(const CurrencyPool & otherPool)
00375     const
00376 {
00377     for (const Amount & amount: currencyAmounts) {
00378         const Amount otherAmount
00379             = otherPool.getAvailable(amount.currencyCode);
00380         if (amount < otherAmount)
00381             return false;
00382     }
00383 
00384     return true;
00385 }
00386 
00387 /*****************************************************************************/
00388 /* LINE ITEMS                                                                */
00389 /*****************************************************************************/
00390 
00391 
00392 LineItems
00393 LineItems::
00394 fromJson(const Json::Value & json)
00395 {
00396     LineItems result;
00397 
00398     for (auto it = json.begin(), end = json.end();  it != end;  ++it)
00399         result[it.memberName()] = CurrencyPool::fromJson(*it);
00400 
00401     return result;
00402 }
00403 
00404 Json::Value
00405 LineItems::
00406 toJson() const
00407 {
00408     Json::Value result(Json::objectValue);
00409     for (auto & e: entries)
00410         result[e.first] = e.second.toJson();
00411     return result;
00412 }
00413 
00414 bool
00415 LineItems::
00416 operator == (const LineItems & other) const
00417 {
00418     auto compareLineItems = [] (const LineItems & li1,
00419                                 const LineItems & li2)
00420         {
00421             for (auto & e: li1.entries) {
00422                 if (e.second != li2[e.first])
00423                     return false;
00424             }
00425             return true;
00426         };
00427 
00428     return compareLineItems(*this, other) && compareLineItems(other, *this);
00429 }
00430 
00431 LineItems &
00432 LineItems::
00433 operator += (const LineItems & other)
00434 {
00435     for (auto & e: other.entries)
00436         (*this)[e.first] += e.second;
00437     return *this;
00438 }
00439 
00440 void
00441 LineItems::
00442 serialize(ML::DB::Store_Writer & store) const
00443 {
00444     store << (unsigned char)0 // version
00445           << entries;
00446 }
00447 
00448 void
00449 LineItems::
00450 reconstitute(ML::DB::Store_Reader & store)
00451 {
00452     unsigned char version;
00453     store >> version;
00454     if (version != 0)
00455         throw ML::Exception("error reconstituting currency pool");
00456     store >> entries;
00457 }
00458 
00459 
00460 /*****************************************************************************/
00461 /* VALUE DESCRIPTIONS                                                        */
00462 /*****************************************************************************/
00463 
00464 struct LineItemsDescription
00465     : public Datacratic::ValueDescriptionT<LineItems> {
00466 
00467     LineItemsDescription()
00468     {
00469     }
00470 
00471     virtual void parseJsonTyped(LineItems * val,
00472                                 JsonParsingContext & context) const
00473     {
00474         *val = std::move(LineItems::fromJson(context.expectJson()));
00475     }
00476 
00477     virtual void printJsonTyped(const LineItems * val,
00478                                 JsonPrintingContext & context) const
00479     {
00480         context.writeJson(val->toJson());
00481     }
00482 
00483     virtual bool isDefaultTyped(const LineItems * val) const
00484     {
00485         return val->empty();
00486     }
00487 };
00488 
00489 struct AmountDescription
00490     : public Datacratic::ValueDescriptionT<Amount> {
00491 
00492     AmountDescription()
00493     {
00494     }
00495 
00496     virtual void parseJsonTyped(Amount * val,
00497                                 JsonParsingContext & context) const
00498     {
00499         *val = std::move(Amount::fromJson(context.expectJson()));
00500         //cerr << "parsing amount JSON" << *val << endl;
00501     }
00502 
00503     virtual void printJsonTyped(const Amount * val,
00504                                 JsonPrintingContext & context) const
00505     {
00506         context.writeJson(val->toJson());
00507     }
00508 
00509     virtual bool isDefaultTyped(const Amount * val) const
00510     {
00511         return val->isZero();
00512     }
00513 };
00514 
00515 struct CurrencyPoolDescription
00516     : public Datacratic::ValueDescriptionT<CurrencyPool> {
00517 
00518     CurrencyPoolDescription()
00519     {
00520     }
00521 
00522     virtual void parseJsonTyped(CurrencyPool * val,
00523                                 JsonParsingContext & context) const
00524     {
00525         *val = std::move(CurrencyPool::fromJson(context.expectJson()));
00526     }
00527 
00528     virtual void printJsonTyped(const CurrencyPool * val,
00529                                 JsonPrintingContext & context) const
00530     {
00531         context.writeJson(val->toJson());
00532     }
00533 
00534     virtual bool isDefaultTyped(const CurrencyPool * val) const
00535     {
00536         return val->empty();
00537     }
00538 };
00539 
00540 struct CurrencyCodeDescription
00541     : public Datacratic::EnumDescription<CurrencyCode> {
00542 
00543     CurrencyCodeDescription()
00544     {
00545     }
00546 
00547     virtual void parseJsonTyped(CurrencyCode * val,
00548                                 JsonParsingContext & context) const
00549     {
00550         std::string s = context.expectStringAscii();
00551         *val = parseCurrencyCode(s);
00552     }
00553 
00554     virtual void printJsonTyped(const CurrencyCode * val,
00555                                 JsonPrintingContext & context) const
00556     {
00557         context.writeString(toString(*val));
00558     }
00559 
00560     virtual bool isDefaultTyped(const CurrencyCode * val) const
00561     {
00562         return false;
00563     }
00564     
00565 };
00566 
00567 ValueDescriptionT<LineItems> * getDefaultDescription(LineItems *)
00568 {
00569     return new LineItemsDescription();
00570 }
00571 
00572 ValueDescriptionT<CurrencyPool> * getDefaultDescription(CurrencyPool *)
00573 {
00574     return new CurrencyPoolDescription();
00575 }
00576 
00577 ValueDescriptionT<Amount> * getDefaultDescription(Amount *)
00578 {
00579     return new AmountDescription();
00580 }
00581 
00582 ValueDescriptionT<CurrencyCode> * getDefaultDescription(CurrencyCode *)
00583 {
00584     return new CurrencyCodeDescription();
00585 }
00586 
00587 } // namespace RTBKIT
00588 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator