@barchart/common-js
Version:
Library of common JavaScript utilities
299 lines (233 loc) • 4.8 kB
JavaScript
const assert = require('./../../lang/assert');
module.exports = (() => {
'use strict';
/**
* A map that is restricted to a certain capacity. If adding an
* item would exceed the capacity; the oldest item is removed.
*
* @public
* @param {Number=} capacity - The maximum number of items the map can contain (defaults to ten).
*/
class EvictingMap {
constructor(capacity) {
assert.argumentIsOptional(capacity, 'capacity', Number);
this._capacity = Math.max((capacity || 0), 0) || 10;
this._map = { };
this._head = null;
this._tail = null;
this._size = 0;
}
/**
* Returns true, if the map contains the item; otherwise false.
*
* @public
* @param {String} key
* @returns {boolean}
*/
has(key) {
return this._map.hasOwnProperty(key);
}
/**
* Puts an item into the map (possibly causing eviction, if the size of the
* list exceeds the capacity).
*
* @public
* @param {String} key
* @param {*} value
*/
put(key, value) {
this.remove(key);
let node;
if (this._head !== null) {
node = this._head.insertBefore(key);
this._head = node;
} else {
node = new Node(key);
this._head = node;
this._tail = node;
}
this._map[key] = new Item(node, key, value);
this._size++;
while (this._size > this._capacity) {
this.remove(this._tail.getItem());
}
}
/**
* Puts an item into the map (possibly causing eviction, if the size of the
* list exceeds the capacity).
*
* @public
* @param {String} key
* @param {*} value
*/
set(key, value) {
this.put(key, value);
}
/**
* Gets an item from the map, returning a null value if the no item
* for the given key exists.
*
* @public
* @param {String} key
* @returns {*|null}
*/
get(key) {
let returnRef;
const item = this._map[key];
if (item) {
returnRef = item.getValue();
const node = item.getNode();
if (node !== this._head) {
if (node === this._tail) {
this._tail = node._previous;
}
node.remove();
this._head = this._head.insertBefore(key);
item.setNode(this._head);
}
} else {
returnRef = null;
}
return returnRef;
}
/**
* Removes an item from the map.
*
* @public
* @param {String} key
*/
remove(key) {
const item = this._map[key];
if (item) {
const node = item.getNode();
const next = node.getNext();
const previous = node.getPrevious();
node.remove();
if (this._head === node) {
this._head = next;
}
if (this._tail === node) {
this._tail = previous;
}
delete this._map[key];
this._size--;
}
}
/**
* Removes an item from the map.
*
* @public
* @param {String} key
*/
delete(key) {
this.remove(key);
}
/**
* Returns true, if the map contains no items; otherwise false.
*
* @public
* @returns {boolean}
*/
empty() {
return this._size === 0;
}
/**
* Returns the number of items stored in the map.
*
* @public
* @returns {Number}
*/
getSize() {
return this._size;
}
/**
* The capacity of the map.
*
* @public
* @returns {Number}
*/
getCapacity() {
return this._capacity;
}
toString() {
return '[EvictingMap]';
}
}
class Item {
constructor(node, key, value) {
this._node = node;
this._key = key;
this._value = value;
}
getKey() {
return this._key;
}
getValue() {
return this._value;
}
getNode() {
return this._node;
}
setNode(node) {
this._node = node;
}
}
class Node {
constructor(item) {
this._item = item;
this._previous = null;
this._next = null;
}
insertBefore(item) {
const node = new Node(item);
node._next = this;
if (this._previous !== null) {
node._previous = this._previous;
this._previous._next = node;
}
this._previous = node;
return node;
}
insertAfter(item) {
const node = new Node(item);
node._previous = this;
if (this._next !== null) {
node._next = this._next;
this._next._previous = node;
}
this._next = node;
return node;
}
remove() {
const next = this._next;
const previous = this._previous;
this._next = null;
this._previous = null;
if (next && previous) {
previous._next = next;
next._previous = previous;
} else if (next) {
next._previous = null;
} else if (previous) {
previous._next = null;
}
return this;
}
getItem() {
return this._item;
}
hasNext() {
return this._next !== null;
}
getNext() {
return this._next;
}
hasPrevious() {
return this._previous !== null;
}
getPrevious() {
return this._previous;
}
}
return EvictingMap;
})();