/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "JSCustomObjectBuilder.h" #include "mozilla/ArrayUtils.h" // for ArrayLength #include "nsDataHashtable.h" #include "nsString.h" #include "nsTArray.h" #include "nsUTF8Utils.h" #if _MSC_VER #define snprintf _snprintf #endif // These are owned and deleted by JSCustomObject struct PropertyValue { virtual ~PropertyValue() {} virtual void SendToStream(std::ostream& stream) = 0; }; template struct finalizer_impl { static void run(T) {} }; template struct finalizer_impl { static void run(T* p) { delete p; } }; template <> struct finalizer_impl { static void run(char* p) { free(p); } }; template class TemplatePropertyValue : public PropertyValue { public: TemplatePropertyValue(T aValue) : mValue(aValue) {} ~TemplatePropertyValue() { finalizer_impl::run(mValue); } virtual void SendToStream(std::ostream& stream); private: T mValue; }; // Escape a UTF8 string to a stream. When an illegal encoding // is found it will insert "INVALID" and the function will return. void EscapeToStream(std::ostream& stream, const char* str) { stream << "\""; size_t len = strlen(str); const char* end = &str[len]; while (str < end) { bool err; const char* utf8CharStart = str; uint32_t ucs4Char = UTF8CharEnumerator::NextChar(&str, end, &err); if (err) { // Encoding error stream << "INVALID\""; return; } // See http://www.ietf.org/rfc/rfc4627.txt?number=4627 // characters that must be escaped: quotation mark, // reverse solidus, and the control characters // (U+0000 through U+001F). if (ucs4Char == '\"') { stream << "\\\""; } else if (ucs4Char == '\\') { stream << "\\\\"; } else if (ucs4Char > 0xFF) { char16_t chr[2]; ConvertUTF8toUTF16 encoder(chr); encoder.write(utf8CharStart, uint32_t(str-utf8CharStart)); char escChar[13]; snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X\\u%04X", chr[0], chr[1]); stream << escChar; } else if (ucs4Char < 0x1F || ucs4Char > 0xFF) { char escChar[7]; snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X", ucs4Char); stream << escChar; } else { stream << char(ucs4Char); } } stream << "\""; } class JSCustomObject { public: JSCustomObject() {} ~JSCustomObject(); friend std::ostream& operator<<(std::ostream& stream, JSCustomObject* entry); template void AddProperty(const char* aName, T aValue) { mProperties.Put(nsDependentCString(aName), new TemplatePropertyValue(aValue)); } nsDataHashtable mProperties; }; class JSCustomArray { public: nsTArray mValues; friend std::ostream& operator<<(std::ostream& stream, JSCustomArray* entry); template void AppendElement(T aValue) { mValues.AppendElement(new TemplatePropertyValue(aValue)); } }; template struct SendToStreamImpl { static void run(std::ostream& stream, const T& t) { stream << t; } }; template struct SendToStreamImpl { static void run(std::ostream& stream, T* t) { stream << *t; } }; template <> struct SendToStreamImpl { static void run(std::ostream& stream, char* p) { EscapeToStream(stream, p); } }; template <> struct SendToStreamImpl { static void run(std::ostream& stream, double p) { // 13 for ms, 16 of microseconds, plus an extra 2 stream.precision(18); stream << p; } }; template <> struct SendToStreamImpl { static void run(std::ostream& stream, JSCustomObject* p) { stream << p; } }; template <> struct SendToStreamImpl { static void run(std::ostream& stream, JSCustomArray* p) { stream << p; } }; template void TemplatePropertyValue::SendToStream(std::ostream& stream) { SendToStreamImpl::run(stream, mValue); } struct JSONStreamClosure { std::ostream& mStream; bool mNeedsComma; }; PLDHashOperator HashTableOutput(const nsACString& aKey, PropertyValue* aValue, void* stream) { JSONStreamClosure& streamClosure = *(JSONStreamClosure*)stream; if (streamClosure.mNeedsComma) { streamClosure.mStream << ","; } streamClosure.mNeedsComma = true; EscapeToStream(streamClosure.mStream, (const char*)aKey.BeginReading()); streamClosure.mStream << ":"; aValue->SendToStream(streamClosure.mStream); return PL_DHASH_NEXT; } std::ostream& operator<<(std::ostream& stream, JSCustomObject* entry) { JSONStreamClosure streamClosure = {stream, false}; stream << "{"; entry->mProperties.EnumerateRead(HashTableOutput, &streamClosure); stream << "}"; return stream; } std::ostream& operator<<(std::ostream& stream, JSCustomArray* entry) { bool needsComma = false; stream << "["; for (uint32_t i = 0; i < entry->mValues.Length(); i++) { if (needsComma) { stream << ","; } entry->mValues[i]->SendToStream(stream); needsComma = true; } stream << "]"; return stream; } PLDHashOperator HashTableFree(const nsACString& aKey, PropertyValue* aValue, void* stream) { delete aValue; return PL_DHASH_NEXT; } JSCustomObject::~JSCustomObject() { mProperties.EnumerateRead(HashTableFree, nullptr); } JSCustomObjectBuilder::JSCustomObjectBuilder() {} void JSCustomObjectBuilder::DeleteObject(JSCustomObject* aObject) { delete aObject; } void JSCustomObjectBuilder::Serialize(JSCustomObject* aObject, std::ostream& stream) { stream << aObject; } void JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue) { aObject->AddProperty(name, aValue); } void JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue) { aObject->AddProperty(name, aValue); } void JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, int aValue) { aObject->AddProperty(name, aValue); } void JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, double aValue) { aObject->AddProperty(name, aValue); } void JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, const char *aValue) { // aValue copy will be freed by the property desctructor (template specialization) aObject->AddProperty(name, strdup(aValue)); } void JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, int aValue) { aArray->AppendElement(aValue); } void JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, const char *aValue) { // aValue copy will be freed by the property desctructor (template specialization) aArray->AppendElement(strdup(aValue)); } void JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject) { aArray->AppendElement(aObject); } JSCustomArray* JSCustomObjectBuilder::CreateArray() { return new JSCustomArray(); } JSCustomObject* JSCustomObjectBuilder::CreateObject() { return new JSCustomObject(); }