using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading;
namespace Jace.Util
{
///
/// An in-memory based cache to store objects. The implementation is thread safe and supports
/// the multiple platforms supported by Jace (.NET, WinRT, WP7 and WP8).
///
/// The type of the keys.
/// The type of the values.
public class MemoryCache
{
private readonly int maximumSize;
private readonly int reductionSize;
private long counter; // We cannot use DateTime.Now, because the precission is not high enough.
private readonly ConcurrentDictionary dictionary;
///
/// Create a new instance of the .
///
/// The maximum allowed number of items in the cache.
/// The number of items to be deleted per cleanup of the cache.
public MemoryCache(int maximumSize, int reductionSize)
{
if (maximumSize < 1)
throw new ArgumentOutOfRangeException("maximumSize",
"The maximum allowed number of items in the cache must be at least one.");
if (reductionSize < 1)
throw new ArgumentOutOfRangeException("reductionSize",
"The cache reduction size must be at least one.");
this.maximumSize = maximumSize;
this.reductionSize = reductionSize;
this.dictionary = new ConcurrentDictionary();
}
///
/// Get the value in the cache for the given key.
///
/// The key to lookup in the cache.
/// The value for the given key.
public TValue this[TKey key]
{
get
{
CacheItem cacheItem = dictionary[key];
cacheItem.Accessed();
return cacheItem.Value;
}
}
///
/// Gets the number of items in the cache.
///
public int Count
{
get
{
return dictionary.Count;
}
}
///
/// Returns true if an item with the given key is present in the cache.
///
/// The key to lookup in the cache.
/// True if an item is present in the cache for the given key.
public bool ContainsKey(TKey key)
{
return dictionary.ContainsKey(key);
}
public bool TryGetValue (TKey key, out TValue result)
{
if (dictionary.TryGetValue(key, out var cachedItem))
{
cachedItem.Accessed();
result = cachedItem.Value;
return true;
}
else
{
result = default(TValue);
return false;
}
}
///
/// If for a given key an item is present in the cache, this method will return
/// the value for the given key. If no item is present in the cache for the given
/// key, the valueFactory is executed to produce the value. This value is stored in
/// the cache and returned to the caller.
///
/// The key to lookup in the cache.
/// The factory to produce the value matching with the key.
/// The value for the given key.
public TValue GetOrAdd(TKey key, Func valueFactory)
{
if (valueFactory == null)
throw new ArgumentNullException("valueFactory");
CacheItem cacheItem = dictionary.GetOrAdd(key, k =>
{
EnsureCacheStorageAvailable();
TValue value = valueFactory(k);
return new CacheItem(this, valueFactory(k));
});
return cacheItem.Value;
}
///
/// Ensure that the cache has room for an additional item.
/// If there is not enough room anymore, force a removal of oldest
/// accessed items in the cache.
///
private void EnsureCacheStorageAvailable()
{
if (dictionary.Count >= maximumSize) // >= because we want to add an item after this method
{
IList keysToDelete = (from p in dictionary.ToArray()
where p.Key != null && p.Value != null
orderby p.Value.LastAccessed ascending
select p.Key).Take(reductionSize).ToList();
foreach (TKey key in keysToDelete)
{
CacheItem cacheItem;
dictionary.TryRemove(key, out cacheItem);
}
}
}
private class CacheItem
{
private MemoryCache cache;
public CacheItem(MemoryCache cache, TValue value)
{
this.cache = cache;
this.Value = value;
Accessed();
}
public TValue Value { get; private set; }
public long LastAccessed { get; private set; }
public void Accessed()
{
this.LastAccessed = Interlocked.Increment(ref cache.counter);
}
}
}
}