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