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;