| // Copyright 2014 Cognitect. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide("com.cognitect.transit.impl.writer"); |
| goog.require("com.cognitect.transit.util"); |
| goog.require("com.cognitect.transit.caching"); |
| goog.require("com.cognitect.transit.handlers"); |
| goog.require("com.cognitect.transit.types"); |
| goog.require("com.cognitect.transit.delimiters"); |
| goog.require("goog.math.Long"); |
| |
| goog.scope(function () { |
| |
| var writer = com.cognitect.transit.impl.writer, |
| util = com.cognitect.transit.util, |
| caching = com.cognitect.transit.caching, |
| handlers = com.cognitect.transit.handlers, |
| types = com.cognitect.transit.types, |
| d = com.cognitect.transit.delimiters, |
| Long = goog.math.Long; |
| |
| writer.escape = function (string) { |
| if (string.length > 0) { |
| var c = string.charAt(0); |
| if (c === d.ESC || c === d.SUB || c === d.RES) { |
| return d.ESC + string; |
| } else { |
| return string; |
| } |
| } else { |
| return string; |
| } |
| }; |
| |
| /** |
| * @constructor |
| */ |
| writer.JSONMarshaller = function Transit$JSONMarshaller(opts) { |
| this.opts = opts || {}; |
| this.preferStrings = this.opts["preferStrings"] != null ? this.opts["preferStrings"] : true; |
| |
| this.objectBuilder = this.opts["objectBuilder"] || null; |
| |
| this.handlers = new handlers.Handlers(); |
| |
| var optsHandlers = this.opts["handlers"]; |
| if (optsHandlers) { |
| if (util.isArray(optsHandlers) || !optsHandlers.forEach) { |
| throw new Error("transit writer \"handlers\" option must be a map"); |
| } |
| var self = this; |
| optsHandlers.forEach(function (v, k) { |
| if (k !== undefined) { |
| self.handlers.set(k, v); |
| } else { |
| throw new Error("Cannot create handler for JavaScript undefined"); |
| } |
| }); |
| } |
| |
| // Multiple JS context helper |
| this.handlerForForeign = this.opts["handlerForForeign"]; |
| |
| this.unpack = this.opts["unpack"] || function (x) { |
| if (types.isArrayMap(x) && x.backingMap === null) { |
| return x._entries; |
| } else { |
| return false; |
| } |
| }; |
| |
| this.verbose = (this.opts && this.opts["verbose"]) || false; |
| }; |
| |
| writer.JSONMarshaller.prototype.handler = function (obj) { |
| var h = this.handlers.get(handlers.constructor(obj)); |
| |
| if (h != null) { |
| return h; |
| } else { |
| var tag = obj && obj["transitTag"]; |
| if (tag) { |
| return this.handlers.get(tag) |
| } else { |
| return null; |
| } |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.registerHandler = function (ctor, handler) { |
| this.handlers.set(ctor, handler); |
| }; |
| |
| writer.JSONMarshaller.prototype.emitNil = function (asMapKey, cache) { |
| if (asMapKey) { |
| return this.emitString(d.ESC, "_", "", asMapKey, cache); |
| } else { |
| return null; |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.emitString = function (prefix, tag, s, asMapKey, cache) { |
| var string = prefix + tag + s; |
| if (cache) { |
| return cache.write(string, asMapKey); |
| } else { |
| return string; |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.emitBoolean = function (b, asMapKey, cache) { |
| if (asMapKey) { |
| var s = b.toString(); |
| return this.emitString(d.ESC, "?", s[0], asMapKey, cache); |
| } else { |
| return b; |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.emitInteger = function (i, asMapKey, cache) { |
| if (i === Infinity) { |
| return this.emitString(d.ESC, "z", "INF", asMapKey, cache); |
| } else if (i === -Infinity) { |
| return this.emitString(d.ESC, "z", "-INF", asMapKey, cache); |
| } else if (isNaN(i)) { |
| return this.emitString(d.ESC, "z", "NaN", asMapKey, cache); |
| } else if (asMapKey || (typeof i === "string") || (i instanceof Long)) { |
| return this.emitString(d.ESC, "i", i.toString(), asMapKey, cache); |
| } else { |
| return i; |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.emitDouble = function (d, asMapKey, cache) { |
| if (asMapKey) { |
| return this.emitString(d.ESC, "d", d, asMapKey, cache); |
| } else { |
| return d; |
| } |
| }; |
| |
| writer.JSONMarshaller.prototype.emitBinary = function (b, asMapKey, cache) { |
| return this.emitString(d.ESC, "b", b, asMapKey, cache); |
| }; |
| |
| writer.JSONMarshaller.prototype.emitQuoted = function (em, obj, cache) { |
| if (em.verbose) { |
| var ret = {}, |
| k = this.emitString(d.ESC_TAG, "'", "", true, cache); |
| ret[k] = writer.marshal(this, obj, false, cache); |
| return ret; |
| } else { |
| return [this.emitString(d.ESC_TAG, "'", "", true, cache), writer.marshal(this, obj, false, cache)]; |
| } |
| }; |
| |
| writer.emitObjects = function (em, iterable, cache) { |
| var ret = []; |
| if (util.isArray(iterable)) { |
| for (var i = 0; i < iterable.length; i++) { |
| ret.push(writer.marshal(em, iterable[i], false, cache)); |
| } |
| } else { |
| iterable.forEach(function (v, i) { |
| ret.push(writer.marshal(em, v, false, cache)); |
| }); |
| } |
| return ret; |
| }; |
| |
| writer.emitArray = function (em, iterable, skip, cache) { |
| return writer.emitObjects(em, iterable, cache); |
| }; |
| |
| writer.isStringableKey = function (em, k) { |
| if (typeof k !== "string") { |
| var h = em.handler(k); |
| return h && h.tag(k).length === 1; |
| } else { |
| return true; |
| } |
| }; |
| |
| /** |
| * Returns true if map-like obj parameter has only stringable keys - |
| * strings, symbols or keywords. If false, obj is a cmap value. |
| * @param em |
| * @param obj |
| * @returns {boolean} |
| */ |
| writer.stringableKeys = function (em, obj) { |
| var arr = em.unpack(obj), |
| stringableKeys = true; |
| |
| if (arr) { |
| for (var i = 0; i < arr.length; i += 2) { |
| stringableKeys = writer.isStringableKey(em, arr[i]); |
| if (!stringableKeys) { |
| break; |
| } |
| } |
| return stringableKeys; |
| } else if (obj.keys) { |
| var iter = obj.keys(), |
| step = null; |
| |
| if (iter.next) { |
| step = iter.next(); |
| while (!step.done) { |
| stringableKeys = writer.isStringableKey(em, step.value); |
| if (!stringableKeys) { |
| break; |
| } |
| step = iter.next(); |
| } |
| return stringableKeys; |
| } |
| } |
| |
| if (obj.forEach) { |
| obj.forEach(function (v, k) { |
| stringableKeys = stringableKeys && writer.isStringableKey(em, k); |
| }); |
| return stringableKeys; |
| } else { |
| throw new Error("Cannot walk keys of object type " + handlers.constructor(obj).name); |
| } |
| }; |
| |
| /** |
| * Returns true if x is an Object instance from a different JavaScript |
| * context. |
| * @param x |
| * @returns {boolean} |
| */ |
| writer.isForeignObject = function (x) { |
| if (x.constructor["transit$isObject"]) { |
| return true; |
| } |
| |
| var ret = x.constructor.toString(); |
| |
| ret = ret.substr('function '.length); |
| ret = ret.substr(0, ret.indexOf('(')); |
| isObject = ret == "Object"; |
| |
| if (typeof Object.defineProperty != "undefined") { |
| Object.defineProperty(x.constructor, "transit$isObject", { |
| value: isObject, |
| enumerable: false |
| }); |
| } else { |
| x.constructor["transit$isObject"] = isObject; |
| } |
| |
| return isObject; |
| }; |
| |
| writer.emitMap = function (em, obj, skip, cache) { |
| var arr = null, rep = null, tag = null, ks = null, i = 0; |
| |
| if ((obj.constructor === Object) || |
| (obj.forEach != null) || |
| (em.handlerForForeign && writer.isForeignObject(obj))) { |
| if (em.verbose) { |
| if (obj.forEach != null) { |
| if (writer.stringableKeys(em, obj)) { |
| var ret = {}; |
| obj.forEach(function (v, k) { |
| ret[writer.marshal(em, k, true, false)] = writer.marshal(em, v, false, cache); |
| }); |
| return ret; |
| } else { |
| arr = em.unpack(obj); |
| rep = []; |
| tag = em.emitString(d.ESC_TAG, "cmap", "", true, cache); |
| if (arr) { |
| for (; i < arr.length; i += 2) { |
| rep.push(writer.marshal(em, arr[i], false, false)); |
| rep.push(writer.marshal(em, arr[i + 1], false, cache)); |
| } |
| } else { |
| obj.forEach(function (v, k) { |
| rep.push(writer.marshal(em, k, false, false)); |
| rep.push(writer.marshal(em, v, false, cache)); |
| }); |
| } |
| ret = {}; |
| ret[tag] = rep; |
| return ret; |
| } |
| } else { |
| ks = util.objectKeys(obj); |
| ret = {}; |
| for (; i < ks.length; i++) { |
| ret[writer.marshal(em, ks[i], true, false)] = writer.marshal(em, obj[ks[i]], false, cache); |
| } |
| return ret; |
| } |
| } else { |
| if (obj.forEach != null) { |
| if (writer.stringableKeys(em, obj)) { |
| arr = em.unpack(obj); |
| ret = ["^ "]; |
| if (arr) { |
| for (; i < arr.length; i += 2) { |
| ret.push(writer.marshal(em, arr[i], true, cache)); |
| ret.push(writer.marshal(em, arr[i + 1], false, cache)); |
| } |
| } else { |
| obj.forEach(function (v, k) { |
| ret.push(writer.marshal(em, k, true, cache)); |
| ret.push(writer.marshal(em, v, false, cache)); |
| }); |
| } |
| return ret; |
| } else { |
| arr = em.unpack(obj); |
| rep = []; |
| tag = em.emitString(d.ESC_TAG, "cmap", "", true, cache); |
| if (arr) { |
| for (; i < arr.length; i += 2) { |
| rep.push(writer.marshal(em, arr[i], false, cache)); |
| rep.push(writer.marshal(em, arr[i + 1], false, cache)); |
| } |
| } else { |
| obj.forEach(function (v, k) { |
| rep.push(writer.marshal(em, k, false, cache)); |
| rep.push(writer.marshal(em, v, false, cache)); |
| }); |
| } |
| return [tag, rep]; |
| } |
| } else { |
| ret = ["^ "]; |
| ks = util.objectKeys(obj); |
| for (; i < ks.length; i++) { |
| ret.push(writer.marshal(em, ks[i], true, cache)); |
| ret.push(writer.marshal(em, obj[ks[i]], false, cache)); |
| } |
| return ret; |
| } |
| } |
| } else if (em.objectBuilder != null) { |
| return em.objectBuilder(obj, function (k) { |
| return writer.marshal(em, k, true, cache); |
| }, |
| function (v) { |
| return writer.marshal(em, v, false, cache); |
| }); |
| } else { |
| var name = handlers.constructor(obj).name, |
| err = new Error("Cannot write " + name); |
| err.data = {obj: obj, type: name}; |
| throw err; |
| } |
| }; |
| |
| writer.emitTaggedMap = function (em, tag, rep, skip, cache) { |
| if (em.verbose) { |
| var ret = {}; |
| ret[em.emitString(d.ESC_TAG, tag, "", true, cache)] = writer.marshal(em, rep, false, cache); |
| return ret; |
| } else { |
| return [em.emitString(d.ESC_TAG, tag, "", true, cache), writer.marshal(em, rep, false, cache)]; |
| } |
| }; |
| |
| writer.emitEncoded = function (em, h, tag, rep, obj, asMapKey, cache) { |
| if (tag.length === 1) { |
| if (typeof rep === "string") { |
| return em.emitString(d.ESC, tag, rep, asMapKey, cache); |
| } else if (asMapKey || em.preferStrings) { |
| var vh = em.verbose && h.getVerboseHandler(); |
| if (vh) { |
| tag = vh.tag(obj); |
| rep = vh.stringRep(obj, vh); |
| } else { |
| rep = h.stringRep(obj, h); |
| } |
| if (rep !== null) { |
| return em.emitString(d.ESC, tag, rep, asMapKey, cache); |
| } else { |
| var err = new Error("Tag \"" + tag + "\" cannot be encoded as string"); |
| err.data = {tag: tag, rep: rep, obj: obj}; |
| throw err; |
| } |
| } else { |
| return writer.emitTaggedMap(em, tag, rep, asMapKey, cache); |
| } |
| } else { |
| return writer.emitTaggedMap(em, tag, rep, asMapKey, cache); |
| } |
| }; |
| |
| writer.marshal = function (em, obj, asMapKey, cache) { |
| var h = em.handler(obj) || (em.handlerForForeign ? em.handlerForForeign(obj, em.handlers) : null), |
| tag = h ? h.tag(obj) : null, |
| rep = h ? h.rep(obj) : null; |
| |
| if (h != null && tag != null) { |
| switch (tag) { |
| case "_": |
| return em.emitNil(asMapKey, cache); |
| break; |
| case "s": |
| return em.emitString("", "", writer.escape(rep), asMapKey, cache); |
| break; |
| case "?": |
| return em.emitBoolean(rep, asMapKey, cache); |
| break; |
| case "i": |
| return em.emitInteger(rep, asMapKey, cache); |
| break; |
| case "d": |
| return em.emitDouble(rep, asMapKey, cache); |
| break; |
| case "b": |
| return em.emitBinary(rep, asMapKey, cache); |
| break; |
| case "'": |
| return em.emitQuoted(em, rep, cache); |
| break; |
| case "array": |
| return writer.emitArray(em, rep, asMapKey, cache); |
| break; |
| case "map": |
| return writer.emitMap(em, rep, asMapKey, cache); |
| break; |
| default: |
| return writer.emitEncoded(em, h, tag, rep, obj, asMapKey, cache); |
| break; |
| } |
| } else { |
| var name = handlers.constructor(obj).name, |
| err = new Error("Cannot write " + name); |
| err.data = {obj: obj, type: name}; |
| throw err; |
| } |
| }; |
| |
| writer.maybeQuoted = function (em, obj) { |
| var h = em.handler(obj) || (em.handlerForForeign ? em.handlerForForeign(obj, em.handlers) : null); |
| |
| if (h != null) { |
| if (h.tag(obj).length === 1) { |
| return types.quoted(obj); |
| } else { |
| return obj; |
| } |
| } else { |
| var name = handlers.constructor(obj).name, |
| err = new Error("Cannot write " + name); |
| err.data = {obj: obj, type: name}; |
| throw err; |
| } |
| }; |
| |
| writer.marshalTop = function (em, obj, asMapKey, cache) { |
| return JSON.stringify(writer.marshal(em, writer.maybeQuoted(em, obj), asMapKey, cache)); |
| }; |
| |
| /** |
| * @constructor |
| */ |
| writer.Writer = function Transit$Writer(marshaller, options) { |
| this._marshaller = marshaller; |
| this.options = options || {}; |
| if (this.options["cache"] === false) { |
| this.cache = null; |
| } else { |
| this.cache = this.options["cache"] ? this.options["cache"] : new caching.WriteCache(); |
| } |
| }; |
| |
| writer.Writer.prototype.marshaller = function () { |
| return this._marshaller; |
| }; |
| writer.Writer.prototype["marshaller"] = writer.Writer.prototype.marshaller; |
| |
| writer.Writer.prototype.write = function (obj, opts) { |
| var ret = null, |
| ropts = opts || {}, |
| asMapKey = ropts["asMapKey"] || false, |
| cache = this._marshaller.verbose ? false : this.cache; |
| |
| if (ropts["marshalTop"] === false) { |
| ret = writer.marshal(this._marshaller, obj, asMapKey, cache) |
| } else { |
| ret = writer.marshalTop(this._marshaller, obj, asMapKey, cache) |
| } |
| if (this.cache != null) { |
| this.cache.clear(); |
| } |
| return ret; |
| }; |
| writer.Writer.prototype["write"] = writer.Writer.prototype.write; |
| |
| writer.Writer.prototype.register = function (type, handler) { |
| this._marshaller.registerHandler(type, handler); |
| }; |
| writer.Writer.prototype["register"] = writer.Writer.prototype.register; |
| |
| }); |