// 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 Transit$Tag(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 Transit$Decoder(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 }; /** * @param {*} node * @param {*} cache * @param {boolean=} asMapKey * @param {boolean=} tagValue * @returns {*} */ 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); }; });