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