// 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; });