UNPKG

diffusion

Version:

Diffusion JavaScript client

359 lines (269 loc) 8.57 kB
/*eslint valid-jsdoc: "off"*/ var requireNonNull = require('util/require-non-null'); var JSONPointer = require('data/json/json-pointer'); var EMPTY = new JSONPointerMap(); function JSONPointerMap() { this.root = new Entry(null, JSONPointer.ROOT); this.size = 0; var self = this; /** * Add an entry. * <P> * By design, no constraints are applied to the supplied pointer. E.g. it is * possible to add "x/foo" followed by "x/1", even though these * can't be valid paths in a single document. This supports the structural * diff use case where we might "add("x/foo", REMOVE)", then "add("x/1", INSERT)". * * @param pointer the key * @param value may not be null * @returns the previous value */ this.put = function(pointer, value) { requireNonNull(value, "value"); var node = self.root; var i = 0; for (; i < pointer.segments.length;) { var s = pointer.segments[i++]; var c = node.findChild(s); if (c === null) { node = node.addChild(s); break; } node = c; } for (; i < pointer.segments.length; i++) { node = node.addChild(pointer.segments[i]); } return node.setValue(value); }; /** * @returns {boolean} true if this map has a value for pointer */ this.contains = function(pointer) { return self.get(pointer) !== null; }; /** * @returns {Object|Null} the element for pointer, or null if there is no value; */ this.get = function(pointer) { var entry = self.getEntry(pointer); if (entry) { return entry.value; } return null; }; /** * Returns the entry for pointer. Entries reflect the tree structure, and might not have a value. * * @param pointer * @returns {Entry|Null} the entry for pointer, or null if there is no entry */ this.getEntry = function(pointer) { var result = self.root; for (var i = 0; i < pointer.segments.length; ++i) { result = result.findChild(pointer.segments[i]); if (result === null) { return null; } } return result; }; this.descendants = function(pointer) { var result = new JSONPointerMap(); var thisNode = self.root; var resultNode = result.root; var segments = pointer.segments; var length = segments.length; if (length === 0) { return this; } for (var i = 0; i < length - 1; ++i) { var s = segments[i]; thisNode = thisNode.findChild(s); if (thisNode === null) { return EMPTY; } resultNode = resultNode.addChild(s); } var last = thisNode.findChild(segments[length - 1]); if (last === null) { return EMPTY; } resultNode.children.push(last); result.size = last.count(); return result; }; this.intersection = function(pointer) { var result = new JSONPointerMap(); var thisNode = self.root; var resultNode = result.root; var segments = pointer.segments; var length = segments.length; if (length === 0) { return this; } if (thisNode.value !== null) { resultNode.setValue(thisNode.value); } for (var i = 0; i < length - 1; ++i) { var s = segments[i]; thisNode = thisNode.findChild(s); if (thisNode === null) { return result; } resultNode = resultNode.addChild(s); if (thisNode.value !== null) { resultNode.setValue(thisNode.value); } } var last = thisNode.findChild(segments[length - 1]); if (last === null) { return result; } resultNode.children.push(last); result.size += last.count(); return result; }; this.iterator = function() { var stack = [self.root]; return new EntryIterator(function() { for (;;) { var r = stack.shift(); if (r === undefined) { return null; } var n = r.children.length; for (; n > 0; --n) { stack.unshift(r.children[n - 1]); } if (r.value !== null) { return r; } } }); }; /** * @returns a post-order iterator */ this.postOrder = function() { var stack = [new PostOrderState(self.root)]; return new EntryIterator(function() { for (;;) { var r = stack[0]; if (r === undefined) { return null; } var c = r.nextChild(); if (c === null) { stack.shift(); if (r.node.value !== null) { return r.node; } } else if (c.children.length === 0) { return c; } else { stack.unshift(new PostOrderState(c)); } } }); }; this.toString = function() { var parts = ['{']; var iter = self.iterator(); while (iter.hasNext()) { if (parts.length > 1) { parts.push(', '); } parts.push(iter.next()); } parts.push('}'); return parts.join(''); }; /** * Returned through {@link JSONPointerMap#getEntry} and iterators. * <p> * Following a sequence of put operations, all entries will have a value, or * children, or both. Through the {@link Entry#removeDescendants()} method, * it is possible for an entry to have neither a value or children. * @constructor */ function Entry(segment, pointer) { this.segment = segment; this.pointer = pointer; this.children = []; this.value = null; this.count = function() { var result = this.value ? 1 : 0; for (var i = 0; i < this.children.length; ++i) { result += this.children[i].count(); } return result; }; this.addChild = function(s) { var c = new Entry(s, pointer.withSegment(s)); this.children.push(c); return c; }; this.findChild = function(s) { for (var i = 0; i < this.children.length; ++i) { if (s.equals(this.children[i].segment)) { return this.children[i]; } } return null; }; this.setValue = function(newValue) { var previous = this.value; if (previous === null) { self.size++; } this.value = newValue; return previous; }; this.removeDescendants = function() { var removed = 0; for (var i = 0; i < this.children.length; ++i) { removed += this.children[i].count(); } this.children = []; self.size -= removed; return removed; }; this.numberOfChildren = function() { var result = 0; for (var i = 0; i < this.children.length; ++i) { result += (this.children[i].value ? 1 : 0); } return result; }; this.toString = function() { return pointer + "=" + this.value; }; } } function PostOrderState(node) { this.node = node; var nextChild = 0; this.nextChild = function() { var children = node.children; if (nextChild >= children.length) { return null; } return children[nextChild++]; }; } function EntryIterator(nextWithValue) { var next = nextWithValue(); this.hasNext = function() { return next !== null; }; this.next = function() { if (next === null) { throw new Error("No such element"); } var result = next; next = nextWithValue(); return result; }; } module.exports = JSONPointerMap;