492 lines
16 KiB
JavaScript
Executable File
492 lines
16 KiB
JavaScript
Executable File
// 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(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) {
|
|
self.handlers.set(k, v);
|
|
});
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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) {
|
|
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 {
|
|
var arr = em.unpack(obj),
|
|
rep = [],
|
|
tag = em.emitString(d.ESC_TAG, "cmap", "", true, cache);
|
|
if(arr) {
|
|
for(var i = 0; i < arr.length; i+=2) {
|
|
rep.push(writer.marshal(em, arr[i], true, false));
|
|
rep.push(writer.marshal(em, arr[i+1], false, cache));
|
|
}
|
|
} else {
|
|
obj.forEach(function(v, k) {
|
|
rep.push(writer.marshal(em, k, true, false));
|
|
rep.push(writer.marshal(em, v, false, cache));
|
|
});
|
|
}
|
|
var ret = {};
|
|
ret[tag] = rep;
|
|
return ret;
|
|
}
|
|
} else {
|
|
var ret = {},
|
|
ks = util.objectKeys(obj);
|
|
for(var i = 0; 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)) {
|
|
var arr = em.unpack(obj),
|
|
ret = ["^ "];
|
|
if(arr) {
|
|
for(var i = 0; 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 {
|
|
var arr = em.unpack(obj),
|
|
rep = [],
|
|
tag = em.emitString(d.ESC_TAG, "cmap", "", true, cache);
|
|
if(arr) {
|
|
for(var i = 0; i < arr.length; i+=2) {
|
|
rep.push(writer.marshal(em, arr[i], true, cache));
|
|
rep.push(writer.marshal(em, arr[i+1], false, cache));
|
|
}
|
|
} else {
|
|
obj.forEach(function(v, k) {
|
|
rep.push(writer.marshal(em, k, true, cache));
|
|
rep.push(writer.marshal(em, v, false, cache));
|
|
});
|
|
}
|
|
return [tag, rep];
|
|
}
|
|
} else {
|
|
var ret = ["^ "],
|
|
ks = util.objectKeys(obj);
|
|
for(var i = 0; 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(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;
|
|
|
|
});
|