ndn-js-contrib
Version:
Reusable 'Classes' for Named Data Networking: NameTree, PIT, FIB, ContentStore, Interfaces, and Transports
389 lines (307 loc) • 10.6 kB
JavaScript
var Name = require('ndn-js/js/name.js').Name;
function checkNode(functionName, node){
if (!(node && node._isNTN))
throw new Error(functionName + ": invalid argument, requires a NameTreeNode");
}
/**Creates an empty NameTree.
*@constructor
*/
var NameTree = function NameTree (){
this.root = new NameTree.Node(new Name(""));
var self = this;
this[Symbol.iterator] = function NameTree_Iterator(){
var iter;
if (self._traversal_direction + 1 === 0){
throw new Error("NameTree[Symbol.iterator](): must use NameTree.up(prefix)/.left(prefix)/.right(prefix) to set traversal direction");
} else if (self._traversal_direction === NameTree.TRAVERSE_UP){
iter = new Prefix_Iterator(self, self._traversal_prefix, self._skipper());
} else {
iter = new Suffix_Iterator(self, self._traversal_prefix, self._traversal_direction, this._skipper());
}
self._traversal_direction = -1;
self._traversal_prefix = null;
return iter;
};
return this;
};
NameTree.TRAVERSE_LEFT = 0;
NameTree.TRAVERSE_RIGHT = 1;
NameTree.TRAVERSE_UP = 2;
module.exports = NameTree;
/**
* Add a node to the NameTree, recursively populating all parents
* @param {NameTree.Node} node - the NameTree.Node to insert.
* @returns {NameTree} this - the NameTree (for chaining).
*/
NameTree.prototype.insert = function NameTree_insert(node){
var curr = this.root;
while (!curr.equals(node)){
curr = curr.insert(node);
}
curr.insert(node);
return this;
};
/**
* remove a node (if no children) and its ancestors (if empty).
* @param {Name} prefix - the name of the node to delete.
* @returns {Object} any item attatched to the node of that prefix
*/
NameTree.prototype.remove = function NameTree_remove(prefix){
this.up(prefix);
var first = true
, item = null
, removeSuffix;
for (var node of this){
if (first){
item = node.getItem();
node.setItem(null);
}
first = false;
if (removeSuffix)
node.remove(removeSuffix);
removeSuffix = false;
if (node.isEmpty() && node.prefix.size() > 0)
removeSuffix = node.prefix.get(-1);
else {
node.updateDepth(~(prefix.size() - node.prefix.size()));
}
}
if (removeSuffix)
this.root.remove(removeSuffix);
else
this.root.updateDepth(~prefix.size())
return item;
};
/**
* Perform a lookup on the NameTree and return the proper node, creating it if necessary.
* @param {Name|URI} prefix the name of the node to lookup.
* @returns {NameTreeNode} the resulting node.
*/
NameTree.prototype.get = function NameTree_get(prefix) {
var node = new NameTree.Node(prefix);
var curr = this.root;
while (!curr.equals(node)){
curr = curr.insert(node);
}
node = curr;
return node;
};
function Prefix_Iterator(nameTree, prefix, skip){
var node = new NameTree.Node(prefix);
var curr = nameTree.root;
this._stack = [];
while (!curr.equals(node)){
if (!skip(curr))
this._stack.push(curr)
curr = curr.insert(node);
}
if (!skip(curr))
this._stack.push(curr)
}
Prefix_Iterator.prototype.next = function NameTree_Iterator_next (){
var done = (this._stack.length === 0);
if(!done)
var next = this._stack.pop();
return {
value : next
, done : done
};
};
/**
* Return an Iterator that provides a .next() method which returns the next longest Prefix matching the selector, returning null when depleted.
* @param {Name} prefix - the prefix to begin iteration
* @param {Function} selector - a selector function that returns a boolean when called with selector(node)
* @returns {Object} Iterator - the .depleted property of the iterator will be true when there are no more matches.
*/
function Suffix_Iterator(nameTree, prefix, _reverse, skip){
this._stack = [ (_reverse) ?
nameTree.get(prefix)._reverse()[Symbol.iterator]()
: nameTree.get(prefix)[Symbol.iterator]() ];
this._reverse = _reverse;
this.skip = skip;
this.first = [nameTree.get(prefix)];
return this;
}
Suffix_Iterator.prototype.next = function Suffix_Iterator_next(){
if (this.first.length){
return (!this.skip(this.first[0])) ? {value:this.first.pop(),done:false}
: {value:null, done: true};
}
var iter = this._stack.pop();
while (iter){
do{
var next = iter.next()
if (next.done)
break;
} while (this.skip(next.value))
if (!next.done){
this._stack.push(iter)
this._stack.push((this._reverse) ?
next.value._reverse()[Symbol.iterator]()
: next.value[Symbol.iterator]())
return next;
} else {
iter = this._stack.pop();
}
}
return {value:null,done:true};
};
/**
*
*@param {Function} skip - a predicate that returns true if you want to skip a node, default returns false (ie all nodes on the walk are returned)
*/
NameTree.prototype.skip = function NameTree_skip(skip){
var self = this;
this._skipper = function _skipper(){
self._skipper = NameTree.prototype._skipper;
return skip;
};
};
NameTree.prototype._skipper = function NameTree__skipper(){
return function skip(){
return false;
};
};
/**
* configure the *next* constructed iterator to traverse UP, starting from a given node resolving at the root
* @param {Name} prefix - the prefix of the node to begin iterating at.
* @param {Function} skip - a predicate that returns true if you want to skip a node, default returns false (ie all nodes on the walk are returned)
*/
NameTree.prototype.up = function NameTree_up(prefix, skip){
this._traversal_direction = NameTree.TRAVERSE_UP;
this._traversal_prefix = prefix || this.head;
};
/**
* configure the *next* constructed iterator to traverse left(down), starting from a given node resolving at the
* rightmost child. the skip parameter allows you to skip ENTIRE BRANCHES of the tree. if you want to just hop over
* nodes, do it manually in your loop body.
* @param {Name} prefix - the prefix of the node to begin iterating at.
*/
NameTree.prototype.left = function NameTree_left(prefix){
this._traversal_direction = NameTree.TRAVERSE_LEFT;
this._traversal_prefix = prefix || this.root.prefix;
};
/**
* configure the *next* constructed iterator to traverse right(down), starting from a given node resolving at the
* LEFTmost child. the skip parameter allows you to skip ENTIRE BRANCHES of the tree. if you want to just hop over
* nodes, do it manually in your loop body.
* @param {Name} prefix - the prefix of the node to begin iterating at.
*/
NameTree.prototype.right = function NameTree_right(prefix){
this._traversal_direction = NameTree.TRAVERSE_RIGHT;
this._traversal_prefix = prefix || this.root.prefix;
};
NameTree.prototype._traversal_direction = -1;
NameTree.prototype._traversal_start = null;
NameTree.Node = function NameTree_Node(prefix, item) {
this.item = item;
this.children = [];
this.prefix = prefix;
this.depth = 0;
var self = this;
this[Symbol.iterator] = function NameTree_Node_Iterator(){
var iter = new Child_Iterator(self, self._traversal_direction, self._skipper());
self._traversal_direction = NameTree.TRAVERSE_LEFT;
return iter;
};
return this;
};
NameTree.Node.prototype.depth = function NameTree_Node_depth (){
return this.depth;
};
NameTree.Node.prototype.skip = NameTree.prototype.skip;
NameTree.Node.prototype._skipper = NameTree.prototype._skipper;
NameTree.Node.prototype.equals = function NameTree_Node_equals (node){
return this.prefix.equals(node.prefix);
};
NameTree.Node.prototype.getItem = function NameTree_Node_getItem (){
return this.item;
};
NameTree.Node.prototype.setItem = function NameTree_Node_setItem (item){
this.item = item;
};
NameTree.Node.prototype.indexOf = function NameTree_Node_indexOf (suffix){
var min = 0
, max = this.children.length - 1
, guess
, comparison;
while (min <= max) {
guess = Math.floor((min + max) / 2);
if (this.children[guess].prefix.get(-1).equals(suffix)) {
return guess;
} else {
comparison = this.children[guess].prefix.get(-1).compare(suffix);
if (comparison < 0)
min = ++guess;
else
max = --guess;
}
}
return ~(max + 1);
};
NameTree.Node.prototype.insert = function NameTree_Node_insert (node){
if (this.equals(node)){
if (this.item && node.item)
throw new Error("NameTree.Node.insert: Already have that node with an Item");
else if (!this.getItem() && node.getItem()){
this.setItem(node.getItem())
}
return this;
}
var suffix = node.prefix.get(this.prefix.size())
, childIndex = this.indexOf(suffix)
, insertion;
if (childIndex < 0){
childIndex = ~childIndex;
insertion = new NameTree.Node(node.prefix.getPrefix(this.prefix.size() + 1));
this.children.splice(childIndex, 0, insertion);
} else {
insertion = this.children[childIndex];
}
this.updateDepth(node.prefix.size() - this.prefix.size());
return insertion;
};
NameTree.Node.prototype.updateDepth = function NameTree_Node_updateDepth (depth){
this.depth = (depth && depth > this.depth) ?
depth
: (depth) ?
this.depth
: (this.children.length === 0) ?
0
: Math.max.apply(null,this.children.map(function(child){return child.depth;})) + 1;
};
NameTree.Node.prototype.remove = function NameTree_Node_remove (suffix){
var childIndex = this.indexOf(suffix)
, node;
if (childIndex < 0)
node = null;
else
node = this.children.splice(childIndex, 1)[0];
this.updateDepth();
return node;
};
NameTree.Node.prototype._reverse = function NameTree_Node__reverse(){
this._traversal_direction = NameTree.TRAVERSE_RIGHT;
return this;
};
NameTree.Node.prototype.isEmpty = function NameTree_Node_isEmpty (){
return (!this.children.length && !this.item);
};
NameTree.Node.prototype._traversal_direction = NameTree.TRAVERSE_LEFT;
function Child_Iterator (node, direction, skip){
this.direction = direction;
this.children = node.children.slice(0);
this.skip = skip;
}
Child_Iterator.prototype.next = function(){
var next;
do {
next = (!this.direction) ?
this.children.shift()
: this.children.pop();
} while (next && this.skip(next));
return {
done: (!next && !this.children.length) ? true : false
, value : next
};
};