API Docs for: 0.8.812

src/com/cognitect/transit/impl/writer.js

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

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;

});