| 1 | /* | 
|---|
| 2 | * Copyright 2017 Google Inc. | 
|---|
| 3 | * | 
|---|
| 4 | * Use of this source code is governed by a BSD-style license that can be | 
|---|
| 5 | * found in the LICENSE file. | 
|---|
| 6 | */ | 
|---|
| 7 |  | 
|---|
| 8 | #ifndef SkJSONWriter_DEFINED | 
|---|
| 9 | #define SkJSONWriter_DEFINED | 
|---|
| 10 |  | 
|---|
| 11 | #include "include/core/SkStream.h" | 
|---|
| 12 | #include "include/private/SkNoncopyable.h" | 
|---|
| 13 | #include "include/private/SkTArray.h" | 
|---|
| 14 |  | 
|---|
| 15 | /** | 
|---|
| 16 | *  Lightweight class for writing properly structured JSON data. No random-access, everything must | 
|---|
| 17 | *  be generated in-order. The resulting JSON is written directly to the SkWStream supplied at | 
|---|
| 18 | *  construction time. Output is buffered, so writing to disk (via an SkFILEWStream) is ideal. | 
|---|
| 19 | * | 
|---|
| 20 | *  There is a basic state machine to ensure that JSON is structured correctly, and to allow for | 
|---|
| 21 | *  (optional) pretty formatting. | 
|---|
| 22 | * | 
|---|
| 23 | *  This class adheres to the RFC-4627 usage of JSON (not ECMA-404). In other words, all JSON | 
|---|
| 24 | *  created with this class must have a top-level object or array. Free-floating values of other | 
|---|
| 25 | *  types are not considered valid. | 
|---|
| 26 | * | 
|---|
| 27 | *  Note that all error checking is in the form of asserts - invalid usage in a non-debug build | 
|---|
| 28 | *  will simply produce invalid JSON. | 
|---|
| 29 | */ | 
|---|
| 30 | class SkJSONWriter : SkNoncopyable { | 
|---|
| 31 | public: | 
|---|
| 32 | enum class Mode { | 
|---|
| 33 | /** | 
|---|
| 34 | *  Output the minimal amount of text. No additional whitespace (including newlines) is | 
|---|
| 35 | *  generated. The resulting JSON is suitable for fast parsing and machine consumption. | 
|---|
| 36 | */ | 
|---|
| 37 | kFast, | 
|---|
| 38 |  | 
|---|
| 39 | /** | 
|---|
| 40 | *  Output human-readable JSON, with indented  objects and arrays, and one value per line. | 
|---|
| 41 | *  Slightly slower than kFast, and produces data that is somewhat larger. | 
|---|
| 42 | */ | 
|---|
| 43 | kPretty | 
|---|
| 44 | }; | 
|---|
| 45 |  | 
|---|
| 46 | /** | 
|---|
| 47 | *  Construct a JSON writer that will serialize all the generated JSON to 'stream'. | 
|---|
| 48 | */ | 
|---|
| 49 | SkJSONWriter(SkWStream* stream, Mode mode = Mode::kFast) | 
|---|
| 50 | : fBlock(new char[kBlockSize]) | 
|---|
| 51 | , fWrite(fBlock) | 
|---|
| 52 | , fBlockEnd(fBlock + kBlockSize) | 
|---|
| 53 | , fStream(stream) | 
|---|
| 54 | , fMode(mode) | 
|---|
| 55 | , fState(State::kStart) { | 
|---|
| 56 | fScopeStack.push_back(Scope::kNone); | 
|---|
| 57 | fNewlineStack.push_back(true); | 
|---|
| 58 | } | 
|---|
| 59 |  | 
|---|
| 60 | ~SkJSONWriter() { | 
|---|
| 61 | this->flush(); | 
|---|
| 62 | delete[] fBlock; | 
|---|
| 63 | SkASSERT(fScopeStack.count() == 1); | 
|---|
| 64 | SkASSERT(fNewlineStack.count() == 1); | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | /** | 
|---|
| 68 | *  Force all buffered output to be flushed to the underlying stream. | 
|---|
| 69 | */ | 
|---|
| 70 | void flush() { | 
|---|
| 71 | if (fWrite != fBlock) { | 
|---|
| 72 | fStream->write(fBlock, fWrite - fBlock); | 
|---|
| 73 | fWrite = fBlock; | 
|---|
| 74 | } | 
|---|
| 75 | } | 
|---|
| 76 |  | 
|---|
| 77 | /** | 
|---|
| 78 | *  Append the name (key) portion of an object member. Must be called between beginObject() and | 
|---|
| 79 | *  endObject(). If you have both the name and value of an object member, you can simply call | 
|---|
| 80 | *  the two argument versions of the other append functions. | 
|---|
| 81 | */ | 
|---|
| 82 | void appendName(const char* name) { | 
|---|
| 83 | if (!name) { | 
|---|
| 84 | return; | 
|---|
| 85 | } | 
|---|
| 86 | SkASSERT(Scope::kObject == this->scope()); | 
|---|
| 87 | SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState); | 
|---|
| 88 | if (State::kObjectValue == fState) { | 
|---|
| 89 | this->write( ",", 1); | 
|---|
| 90 | } | 
|---|
| 91 | this->separator(this->multiline()); | 
|---|
| 92 | this->write( "\"", 1); | 
|---|
| 93 | this->write(name, strlen(name)); | 
|---|
| 94 | this->write( "\":", 2); | 
|---|
| 95 | fState = State::kObjectName; | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | /** | 
|---|
| 99 | *  Adds a new object. A name must be supplied when called between beginObject() and | 
|---|
| 100 | *  endObject(). Calls to beginObject() must be balanced by corresponding calls to endObject(). | 
|---|
| 101 | *  By default, objects are written out with one named value per line (when in kPretty mode). | 
|---|
| 102 | *  This can be overridden for a particular object by passing false for multiline, this will | 
|---|
| 103 | *  keep the entire object on a single line. This can help with readability in some situations. | 
|---|
| 104 | *  In kFast mode, this parameter is ignored. | 
|---|
| 105 | */ | 
|---|
| 106 | void beginObject(const char* name = nullptr, bool multiline = true) { | 
|---|
| 107 | this->appendName(name); | 
|---|
| 108 | this->beginValue(true); | 
|---|
| 109 | this->write( "{", 1); | 
|---|
| 110 | fScopeStack.push_back(Scope::kObject); | 
|---|
| 111 | fNewlineStack.push_back(multiline); | 
|---|
| 112 | fState = State::kObjectBegin; | 
|---|
| 113 | } | 
|---|
| 114 |  | 
|---|
| 115 | /** | 
|---|
| 116 | *  Ends an object that was previously started with beginObject(). | 
|---|
| 117 | */ | 
|---|
| 118 | void endObject() { | 
|---|
| 119 | SkASSERT(Scope::kObject == this->scope()); | 
|---|
| 120 | SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState); | 
|---|
| 121 | bool emptyObject = State::kObjectBegin == fState; | 
|---|
| 122 | bool wasMultiline = this->multiline(); | 
|---|
| 123 | this->popScope(); | 
|---|
| 124 | if (!emptyObject) { | 
|---|
| 125 | this->separator(wasMultiline); | 
|---|
| 126 | } | 
|---|
| 127 | this->write( "}", 1); | 
|---|
| 128 | } | 
|---|
| 129 |  | 
|---|
| 130 | /** | 
|---|
| 131 | *  Adds a new array. A name must be supplied when called between beginObject() and | 
|---|
| 132 | *  endObject(). Calls to beginArray() must be balanced by corresponding calls to endArray(). | 
|---|
| 133 | *  By default, arrays are written out with one value per line (when in kPretty mode). | 
|---|
| 134 | *  This can be overridden for a particular array by passing false for multiline, this will | 
|---|
| 135 | *  keep the entire array on a single line. This can help with readability in some situations. | 
|---|
| 136 | *  In kFast mode, this parameter is ignored. | 
|---|
| 137 | */ | 
|---|
| 138 | void beginArray(const char* name = nullptr, bool multiline = true) { | 
|---|
| 139 | this->appendName(name); | 
|---|
| 140 | this->beginValue(true); | 
|---|
| 141 | this->write( "[", 1); | 
|---|
| 142 | fScopeStack.push_back(Scope::kArray); | 
|---|
| 143 | fNewlineStack.push_back(multiline); | 
|---|
| 144 | fState = State::kArrayBegin; | 
|---|
| 145 | } | 
|---|
| 146 |  | 
|---|
| 147 | /** | 
|---|
| 148 | *  Ends an array that was previous started with beginArray(). | 
|---|
| 149 | */ | 
|---|
| 150 | void endArray() { | 
|---|
| 151 | SkASSERT(Scope::kArray == this->scope()); | 
|---|
| 152 | SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState); | 
|---|
| 153 | bool emptyArray = State::kArrayBegin == fState; | 
|---|
| 154 | bool wasMultiline = this->multiline(); | 
|---|
| 155 | this->popScope(); | 
|---|
| 156 | if (!emptyArray) { | 
|---|
| 157 | this->separator(wasMultiline); | 
|---|
| 158 | } | 
|---|
| 159 | this->write( "]", 1); | 
|---|
| 160 | } | 
|---|
| 161 |  | 
|---|
| 162 | /** | 
|---|
| 163 | *  Functions for adding values of various types. The single argument versions add un-named | 
|---|
| 164 | *  values, so must be called either | 
|---|
| 165 | *  - Between beginArray() and endArray()                                -or- | 
|---|
| 166 | *  - Between beginObject() and endObject(), after calling appendName() | 
|---|
| 167 | */ | 
|---|
| 168 | void appendString(const char* value) { | 
|---|
| 169 | this->beginValue(); | 
|---|
| 170 | this->write( "\"", 1); | 
|---|
| 171 | if (value) { | 
|---|
| 172 | while (*value) { | 
|---|
| 173 | switch (*value) { | 
|---|
| 174 | case '"': this->write( "\\\"", 2); break; | 
|---|
| 175 | case '\\': this->write( "\\\\", 2); break; | 
|---|
| 176 | case '\b': this->write( "\\b", 2); break; | 
|---|
| 177 | case '\f': this->write( "\\f", 2); break; | 
|---|
| 178 | case '\n': this->write( "\\n", 2); break; | 
|---|
| 179 | case '\r': this->write( "\\r", 2); break; | 
|---|
| 180 | case '\t': this->write( "\\t", 2); break; | 
|---|
| 181 | default: this->write(value, 1); break; | 
|---|
| 182 | } | 
|---|
| 183 | value++; | 
|---|
| 184 | } | 
|---|
| 185 | } | 
|---|
| 186 | this->write( "\"", 1); | 
|---|
| 187 | } | 
|---|
| 188 |  | 
|---|
| 189 | void appendPointer(const void* value) { this->beginValue(); this->appendf( "\"%p\"", value); } | 
|---|
| 190 | void appendBool(bool value) { | 
|---|
| 191 | this->beginValue(); | 
|---|
| 192 | if (value) { | 
|---|
| 193 | this->write( "true", 4); | 
|---|
| 194 | } else { | 
|---|
| 195 | this->write( "false", 5); | 
|---|
| 196 | } | 
|---|
| 197 | } | 
|---|
| 198 | void appendS32(int32_t value) { this->beginValue(); this->appendf( "%d", value); } | 
|---|
| 199 | void appendS64(int64_t value); | 
|---|
| 200 | void appendU32(uint32_t value) { this->beginValue(); this->appendf( "%u", value); } | 
|---|
| 201 | void appendU64(uint64_t value); | 
|---|
| 202 | void appendFloat(float value) { this->beginValue(); this->appendf( "%g", value); } | 
|---|
| 203 | void appendDouble(double value) { this->beginValue(); this->appendf( "%g", value); } | 
|---|
| 204 | void appendFloatDigits(float value, int digits) { | 
|---|
| 205 | this->beginValue(); | 
|---|
| 206 | this->appendf( "%.*g", digits, value); | 
|---|
| 207 | } | 
|---|
| 208 | void appendDoubleDigits(double value, int digits) { | 
|---|
| 209 | this->beginValue(); | 
|---|
| 210 | this->appendf( "%.*g", digits, value); | 
|---|
| 211 | } | 
|---|
| 212 | void appendHexU32(uint32_t value) { this->beginValue(); this->appendf( "\"0x%x\"", value); } | 
|---|
| 213 | void appendHexU64(uint64_t value); | 
|---|
| 214 |  | 
|---|
| 215 | #define DEFINE_NAMED_APPEND(function, type) \ | 
|---|
| 216 | void function(const char* name, type value) { this->appendName(name); this->function(value); } | 
|---|
| 217 |  | 
|---|
| 218 | /** | 
|---|
| 219 | *  Functions for adding named values of various types. These add a name field, so must be | 
|---|
| 220 | *  called between beginObject() and endObject(). | 
|---|
| 221 | */ | 
|---|
| 222 | DEFINE_NAMED_APPEND(appendString, const char *) | 
|---|
| 223 | DEFINE_NAMED_APPEND(appendPointer, const void *) | 
|---|
| 224 | DEFINE_NAMED_APPEND(appendBool, bool) | 
|---|
| 225 | DEFINE_NAMED_APPEND(appendS32, int32_t) | 
|---|
| 226 | DEFINE_NAMED_APPEND(appendS64, int64_t) | 
|---|
| 227 | DEFINE_NAMED_APPEND(appendU32, uint32_t) | 
|---|
| 228 | DEFINE_NAMED_APPEND(appendU64, uint64_t) | 
|---|
| 229 | DEFINE_NAMED_APPEND(appendFloat, float) | 
|---|
| 230 | DEFINE_NAMED_APPEND(appendDouble, double) | 
|---|
| 231 | DEFINE_NAMED_APPEND(appendHexU32, uint32_t) | 
|---|
| 232 | DEFINE_NAMED_APPEND(appendHexU64, uint64_t) | 
|---|
| 233 |  | 
|---|
| 234 | #undef DEFINE_NAMED_APPEND | 
|---|
| 235 |  | 
|---|
| 236 | void appendFloatDigits(const char* name, float value, int digits) { | 
|---|
| 237 | this->appendName(name); | 
|---|
| 238 | this->appendFloatDigits(value, digits); | 
|---|
| 239 | } | 
|---|
| 240 | void appendDoubleDigits(const char* name, double value, int digits) { | 
|---|
| 241 | this->appendName(name); | 
|---|
| 242 | this->appendDoubleDigits(value, digits); | 
|---|
| 243 | } | 
|---|
| 244 |  | 
|---|
| 245 | private: | 
|---|
| 246 | enum { | 
|---|
| 247 | // Using a 32k scratch block gives big performance wins, but we diminishing returns going | 
|---|
| 248 | // any larger. Even with a 1MB block, time to write a large (~300 MB) JSON file only drops | 
|---|
| 249 | // another ~10%. | 
|---|
| 250 | kBlockSize = 32 * 1024, | 
|---|
| 251 | }; | 
|---|
| 252 |  | 
|---|
| 253 | enum class Scope { | 
|---|
| 254 | kNone, | 
|---|
| 255 | kObject, | 
|---|
| 256 | kArray | 
|---|
| 257 | }; | 
|---|
| 258 |  | 
|---|
| 259 | enum class State { | 
|---|
| 260 | kStart, | 
|---|
| 261 | kEnd, | 
|---|
| 262 | kObjectBegin, | 
|---|
| 263 | kObjectName, | 
|---|
| 264 | kObjectValue, | 
|---|
| 265 | kArrayBegin, | 
|---|
| 266 | kArrayValue, | 
|---|
| 267 | }; | 
|---|
| 268 |  | 
|---|
| 269 | void appendf(const char* fmt, ...); | 
|---|
| 270 |  | 
|---|
| 271 | void beginValue(bool structure = false) { | 
|---|
| 272 | SkASSERT(State::kObjectName == fState || | 
|---|
| 273 | State::kArrayBegin == fState || | 
|---|
| 274 | State::kArrayValue == fState || | 
|---|
| 275 | (structure && State::kStart == fState)); | 
|---|
| 276 | if (State::kArrayValue == fState) { | 
|---|
| 277 | this->write( ",", 1); | 
|---|
| 278 | } | 
|---|
| 279 | if (Scope::kArray == this->scope()) { | 
|---|
| 280 | this->separator(this->multiline()); | 
|---|
| 281 | } else if (Scope::kObject == this->scope() && Mode::kPretty == fMode) { | 
|---|
| 282 | this->write( " ", 1); | 
|---|
| 283 | } | 
|---|
| 284 | // We haven't added the value yet, but all (non-structure) callers emit something | 
|---|
| 285 | // immediately, so transition state, to simplify the calling code. | 
|---|
| 286 | if (!structure) { | 
|---|
| 287 | fState = Scope::kArray == this->scope() ? State::kArrayValue : State::kObjectValue; | 
|---|
| 288 | } | 
|---|
| 289 | } | 
|---|
| 290 |  | 
|---|
| 291 | void separator(bool multiline) { | 
|---|
| 292 | if (Mode::kPretty == fMode) { | 
|---|
| 293 | if (multiline) { | 
|---|
| 294 | this->write( "\n", 1); | 
|---|
| 295 | for (int i = 0; i < fScopeStack.count() - 1; ++i) { | 
|---|
| 296 | this->write( "   ", 3); | 
|---|
| 297 | } | 
|---|
| 298 | } else { | 
|---|
| 299 | this->write( " ", 1); | 
|---|
| 300 | } | 
|---|
| 301 | } | 
|---|
| 302 | } | 
|---|
| 303 |  | 
|---|
| 304 | void write(const char* buf, size_t length) { | 
|---|
| 305 | if (static_cast<size_t>(fBlockEnd - fWrite) < length) { | 
|---|
| 306 | // Don't worry about splitting writes that overflow our block. | 
|---|
| 307 | this->flush(); | 
|---|
| 308 | } | 
|---|
| 309 | if (length > kBlockSize) { | 
|---|
| 310 | // Send particularly large writes straight through to the stream (unbuffered). | 
|---|
| 311 | fStream->write(buf, length); | 
|---|
| 312 | } else { | 
|---|
| 313 | memcpy(fWrite, buf, length); | 
|---|
| 314 | fWrite += length; | 
|---|
| 315 | } | 
|---|
| 316 | } | 
|---|
| 317 |  | 
|---|
| 318 | Scope scope() const { | 
|---|
| 319 | SkASSERT(!fScopeStack.empty()); | 
|---|
| 320 | return fScopeStack.back(); | 
|---|
| 321 | } | 
|---|
| 322 |  | 
|---|
| 323 | bool multiline() const { | 
|---|
| 324 | SkASSERT(!fNewlineStack.empty()); | 
|---|
| 325 | return fNewlineStack.back(); | 
|---|
| 326 | } | 
|---|
| 327 |  | 
|---|
| 328 | void popScope() { | 
|---|
| 329 | fScopeStack.pop_back(); | 
|---|
| 330 | fNewlineStack.pop_back(); | 
|---|
| 331 | switch (this->scope()) { | 
|---|
| 332 | case Scope::kNone: | 
|---|
| 333 | fState = State::kEnd; | 
|---|
| 334 | break; | 
|---|
| 335 | case Scope::kObject: | 
|---|
| 336 | fState = State::kObjectValue; | 
|---|
| 337 | break; | 
|---|
| 338 | case Scope::kArray: | 
|---|
| 339 | fState = State::kArrayValue; | 
|---|
| 340 | break; | 
|---|
| 341 | default: | 
|---|
| 342 | SkDEBUGFAIL( "Invalid scope"); | 
|---|
| 343 | break; | 
|---|
| 344 | } | 
|---|
| 345 | } | 
|---|
| 346 |  | 
|---|
| 347 | char* fBlock; | 
|---|
| 348 | char* fWrite; | 
|---|
| 349 | char* fBlockEnd; | 
|---|
| 350 |  | 
|---|
| 351 | SkWStream* fStream; | 
|---|
| 352 | Mode fMode; | 
|---|
| 353 | State fState; | 
|---|
| 354 | SkSTArray<16, Scope, true> fScopeStack; | 
|---|
| 355 | SkSTArray<16, bool, true> fNewlineStack; | 
|---|
| 356 | }; | 
|---|
| 357 |  | 
|---|
| 358 | #endif | 
|---|
| 359 |  | 
|---|