API Docs for: 0.8.812

src/com/cognitect/transit/impl/decoder.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.decoder");
goog.require("com.cognitect.transit.util");
goog.require("com.cognitect.transit.delimiters");
goog.require("com.cognitect.transit.caching");
goog.require("com.cognitect.transit.types");

goog.scope(function() {

var decoder = com.cognitect.transit.impl.decoder,
    util    = com.cognitect.transit.util,
    d       = com.cognitect.transit.delimiters,
    caching = com.cognitect.transit.caching,
    types   = com.cognitect.transit.types;

// =============================================================================
// Decoder

/**
 * @constructor
 */
decoder.Tag = function(s) {
    this.str = s;
};

decoder.tag = function(s) {
    return new decoder.Tag(s);
};

decoder.isTag = function(x) {
    return x && (x instanceof decoder.Tag);
};

decoder.isGroundHandler = function(handler) {
    switch(handler) {
        case "_":
        case "s":
        case "?":
        case "i":
        case "d":
        case "b":
        case "'":
        case "array":
        case "map":
        return true;
    }
    return false;
};

/**
 * A transit decoder
 * @constructor
 */
decoder.Decoder = function(options) {
    this.options = options || {};
    this.handlers = {};
    for(var h in this.defaults.handlers) {
        this.handlers[h] = this.defaults.handlers[h];
    }
    for(var h in this.options["handlers"]) {
        if(decoder.isGroundHandler(h)) {
            throw new Error("Cannot override handler for ground type \""+h+"\"");
        }
        this.handlers[h] = this.options["handlers"][h];
    }
    this.preferStrings = this.options["preferStrings"] != null ? this.options["preferStrings"] : this.defaults.preferStrings;
    this.preferBuffers = this.options["preferBuffers"] != null ? this.options["preferBuffers"] : this.defaults.preferBuffers;
    this.defaultHandler = this.options["defaultHandler"] || this.defaults.defaultHandler;
    /* NOT PUBLIC */
    this.mapBuilder = this.options["mapBuilder"];
    this.arrayBuilder = this.options["arrayBuilder"];
};


decoder.Decoder.prototype.defaults = {
    handlers: {
        "_": function(v, d) { return types.nullValue(); },
        "?": function(v, d) { return types.boolValue(v); },
        "b": function(v, d) { return types.binary(v, d); },
        "i": function(v, d) { return types.intValue(v); },
        "n": function(v, d) { return types.bigInteger(v); },
        "d": function(v, d) { return types.floatValue(v); },
        "f": function(v, d) { return types.bigDecimalValue(v); },
        "c": function(v, d) { return types.charValue(v); },
        ":": function(v, d) { return types.keyword(v); },
        "$": function(v, d) { return types.symbol(v); },
        "r": function(v, d) { return types.uri(v); },
        "z": function(v, d) { return types.specialDouble(v); },

        // tagged
        "'": function(v, d) { return v; },
        "m": function(v, d) { return types.date(v); },
        "t": function(v, d) { return types.verboseDate(v); },
        "u": function(v, d) { return types.uuid(v); },
        "set": function(v, d) { return types.set(v); },
        "list": function(v, d) { return types.list(v); },
        "link": function(v, d) { return types.link(v); },
        "cmap": function(v, d) { return types.map(v, false); }
    },
    defaultHandler: function(c, val) {
        return types.taggedValue(c, val);
    },
    preferStrings: true,
    preferBuffers: true
};

decoder.Decoder.prototype.decode = function(node, cache, asMapKey, tagValue) {
    if(node == null) return null;

    var t = typeof node;

    switch(t) {
    case "string":
        return this.decodeString(node, cache, asMapKey, tagValue);
        break;
    case "object":
        if(util.isArray(node)) {
            if(node[0] === "^ ") {
                return this.decodeArrayHash(node, cache, asMapKey, tagValue);
            } else {
                return this.decodeArray(node, cache, asMapKey, tagValue);
            }
        } else {
            return this.decodeHash(node, cache, asMapKey, tagValue);
        }
        break;
    }

    return node;
};
decoder.Decoder.prototype["decode"] = decoder.Decoder.prototype.decode;

decoder.Decoder.prototype.decodeString = function(string, cache, asMapKey, tagValue) {
    if(caching.isCacheable(string, asMapKey)) {
        var val = this.parseString(string, cache, false);
        if(cache) {
            cache.write(val, asMapKey);
        }
        return val;
    } else if(caching.isCacheCode(string)) {
        return cache.read(string, asMapKey);
    } else {
        return this.parseString(string, cache, asMapKey);
    }
};

decoder.Decoder.prototype.decodeHash = function(hash, cache, asMapKey, tagValue) {
    var ks  = util.objectKeys(hash),
        key = ks[0],
        tag = ks.length == 1 ? this.decode(key, cache, false, false) : null;

    if(decoder.isTag(tag)) {
        var val     = hash[key],
            handler = this.handlers[tag.str];
        if(handler != null) {
            return handler(this.decode(val, cache, false, true), this);
        } else {
            return types.taggedValue(tag.str, this.decode(val, cache, false, false));
        }
    } else if(this.mapBuilder) {
        if((ks.length < (types.SMALL_ARRAY_MAP_THRESHOLD*2)) && this.mapBuilder.fromArray) {
            var nodep = [];
            for(var i = 0; i < ks.length; i++) {
                var strKey = ks[i];
                nodep.push(this.decode(strKey, cache, true, false));
                nodep.push(this.decode(hash[strKey], cache, false, false));
            }
            return this.mapBuilder.fromArray(nodep, hash);
        } else {
            var ret = this.mapBuilder.init(hash);
            for(var i = 0; i < ks.length; i++) {
                var strKey = ks[i];
                ret = this.mapBuilder.add(ret,
                                          this.decode(strKey, cache, true, false),
                                          this.decode(hash[strKey], cache, false, false),
                                          hash);
            }
            return this.mapBuilder.finalize(ret, hash);
        }
    } else {
        var nodep = [];

        for(var i = 0; i < ks.length; i++) {
            var strKey = ks[i];
            nodep.push(this.decode(strKey, cache, true, false));
            nodep.push(this.decode(hash[strKey], cache, false, false));
        }

        return types.map(nodep, false);
    }
};

decoder.Decoder.prototype.decodeArrayHash = function(node, cache, asMapKey, tagValue) {
    if(this.mapBuilder) {
        if((node.length < ((types.SMALL_ARRAY_MAP_THRESHOLD*2)+1)) && this.mapBuilder.fromArray) {
            var nodep = [];
            for(var i = 1; i < node.length; i+=2) {
                nodep.push(this.decode(node[i], cache, true, false));
                nodep.push(this.decode(node[i+1], cache, false, false));
            }
            return this.mapBuilder.fromArray(nodep, node);
        } else {
            var ret = this.mapBuilder.init(node);
            for(var i = 1; i < node.length; i+=2) {
                ret = this.mapBuilder.add(ret,
                                          this.decode(node[i], cache, true, false),
                                          this.decode(node[i+1], cache, false, false),
                                          node)
            }
            return this.mapBuilder.finalize(ret, node);
        }
    } else {
        var nodep = [];

        // collect keys
        for(var i = 1; i < node.length; i +=2) {
            nodep.push(this.decode(node[i], cache, true, false));
            nodep.push(this.decode(node[i+1], cache, false, false));
        }

        return types.map(nodep, false);
    }
};

decoder.Decoder.prototype.decodeArray = function(node, cache, asMapKey, tagValue) {
    if(tagValue) {
        var ret = [];
        for(var i = 0; i < node.length; i++) {
            ret.push(this.decode(node[i], cache, asMapKey, false));
        }
        return ret;
    } else {
        var cacheIdx = cache && cache.idx;
        // tagged value as 2-array case
        if((node.length === 2) &&
           (typeof node[0] === "string")) {
            var tag = this.decode(node[0], cache, false, false);
            if(decoder.isTag(tag)) {
                var val     = node[1],
                    handler = this.handlers[tag.str];
                if(handler != null) {
                    var ret = handler(this.decode(val, cache, asMapKey, true), this);
                    return ret;
                } else {
                    return types.taggedValue(tag.str, this.decode(val, cache, asMapKey, false))
                }
            }
        }

        // rewind cache
        if(cache && (cacheIdx != cache.idx)) {
            cache.idx = cacheIdx;
        }

        if(this.arrayBuilder) {
            // NOTE: hard coded for ClojureScript for now - David
            if(node.length <= 32 && this.arrayBuilder.fromArray) {
                var arr = [];
                for(var i = 0; i < node.length; i++) {
                    arr.push(this.decode(node[i], cache, asMapKey, false));
                }
                return this.arrayBuilder.fromArray(arr, node);
            } else {
                var ret = this.arrayBuilder.init(node);
                for(var i = 0; i < node.length; i++) {
                    ret = this.arrayBuilder.add(ret, this.decode(node[i], cache, asMapKey, false), node);
                }
                return this.arrayBuilder.finalize(ret, node);
            }
        } else {
            var ret = [];
            for(var i = 0; i < node.length; i++) {
                ret.push(this.decode(node[i], cache, asMapKey, false));
            }
            return ret;
        }
    }
};

decoder.Decoder.prototype.parseString = function(string, cache, asMapKey) {
    if(string.charAt(0) === d.ESC) {
        var c = string.charAt(1);
        if(c === d.ESC || c === d.SUB || c === d.RES) {
            return string.substring(1);
        } else if (c === d.TAG) {
            return decoder.tag(string.substring(2));
        } else {
            var handler = this.handlers[c];
            if(handler == null) {
                return this.defaultHandler(c, string.substring(2));
            } else {
                return handler(string.substring(2), this);
            }
        }
    } else {
        return string;
    }
};

decoder.decoder = function(options) {
    return new decoder.Decoder(options);
};

});