API Docs for: 1.0.0
Show:

File: src/gallery-mru-cache/js/MRUCache.js

/**
 * @module gallery-mru-cache
 */

/**********************************************************************
 * <p>Cache which drops items based on "most recently used."  Items are
 * dropped when a user-defined criterion is exceeded, e.g., total size or
 * number of items.</p>
 * 
 * <p>The items are stored in a map of {data,mru_item_ref}.  The MRU items
 * are stored in a doubly linked list (which stores the map keys) to allow
 * easy re-ordering and dropping of items.  Every cache hit moves the
 * associated MRU item to the front of the list.</p>
 * 
 * @main gallery-mru-cache
 * @class MRUCache
 * @constructor
 * @param config {Object}
 * @param config.metric {Function} Computes the metric for an item.  It receives the value as an argument and must return a positive number.
 * @param config.limit {Number} Maximum allowed value of the metric.  Items are dropped off the end of the MRU list until the metric is less than or equal to the limit.
 * @param [config.meta] {Function} Attaches meta data to an item when it is added to the cache.  It receives the value as an argument.
 * @param [config.stats] {Boolean} Pass true if you want to collect basic statistics.  Pass a function if you want to control what information is stored for each key.  The function receives the key, the value, and the stat object.
 */
function MRUCache(config)
{
	this._metric_fn = config.metric;
	this._limit     = config.limit;
	this._meta      = config.meta;
	this._stats     = config.stats ? initStats() : null;

	if (Y.Lang.isFunction(config.stats))
	{
		this._stats_key_meta = config.stats;
	}

	this.clear();
}

function initStats()
{
	return { gets: 0, keys: {} };
}

function initKeyStats(keys, key)
{
	if (!keys[key])
	{
		keys[key] = { puts: 0, gets: 0 };
	}
}

MRUCache.prototype =
{
	/**
	 * Retrieve a value.
	 * 
	 * @method get
	 * @param key {String} the key of the object to retrieve
	 * @return {Mixed} the stored object, or undefined if the slot is empty
	 */
	get: function(
		/* string */	key)
	{
		var obj = this._store[key];
		if (obj)
		{
			this._mru.prepend(obj.mru);

			if (this._stats)
			{
				this._stats.gets++;

				initKeyStats(this._stats.keys, key);
				this._stats.keys[key].gets++;
			}

			return obj.data;
		}
	},

	/**
	 * Store a value.
	 * 
	 * @method put
	 * @param key {String} the key of the value
	 * @param value {Object} the value to store
	 * @return {boolean} false if the key has already been used
	 */
	put: function(
		/* string */	key,
		/* obj/fn */	value)
	{
		var exists = !Y.Lang.isUndefined(this._store[key]);
		if (exists)
		{
			return false;
		}

		var obj =
		{
			data: value,
			mru:  this._mru.prepend(key)
		};

		if (this._meta)
		{
			obj.meta = this._meta(value);
		}

		this._store[key] = obj;

		this._metric += this._metric_fn(value);
		while (this._metric > this._limit)
		{
			this.remove(this._mru.tail().value);
		}

		if (this._stats)
		{
			initKeyStats(this._stats.keys, key);
			this._stats.keys[key].puts++;

			if (this._stats_key_meta)
			{
				this._stats_key_meta(key, value, this._stats.keys[key]);
			}
		}

		return true;
	},

	/**
	 * Store a value.
	 * 
	 * @method replace
	 * @param key {String} the key of the value
	 * @param value {Object} the value to store
	 * @return {Mixed} the original value that was in the slot, or undefined if the slot is empty
	 */
	replace: function(
		/* string */	key,
		/* obj/fn */	value)
	{
		var orig = this.remove(key);
		this.put(key, value);
		return orig;
	},

	/**
	 * Remove an value.
	 * 
	 * @method remove
	 * @param key {String} the key of the value
	 * @return {mixed} the value that was removed, or undefined if the slot was empty
	 */
	remove: function(
		/* string */	key)
	{
		var orig = this._store[key];
		delete this._store[key];
		if (orig)
		{
			this._mru.remove(orig.mru);
			this._metric -= this._metric_fn(orig.data);
			return orig.data;
		}
	},

	/**
	 * Remove all values.
	 * 
	 * @method clear
	 */
	clear: function()
	{
		this._store  = {};
		this._mru    = new Y.LinkedList();
		this._metric = 0;
	},

	/**
	 * This resets all the values.
	 *
	 * @method dumpStats
	 * @return {Object} the current stats
	 */
	dumpStats: function()
	{
		var stats   = this._stats;
		this._stats = initStats();
		return stats;
	}
};

Y.MRUCache = MRUCache;