using System; 
 | 
using System.Collections.Generic; 
 | 
using System.Linq; 
 | 
using System.Text; 
 | 
using System.Collections.Concurrent; 
 | 
using System.Threading; 
 | 
  
 | 
namespace Jace.Util 
 | 
{ 
 | 
    /// <summary> 
 | 
    /// 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). 
 | 
    /// </summary> 
 | 
    /// <typeparam name="TKey">The type of the keys.</typeparam> 
 | 
    /// <typeparam name="TValue">The type of the values.</typeparam> 
 | 
    public class MemoryCache<TKey, TValue> 
 | 
    { 
 | 
        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<TKey, CacheItem> dictionary; 
 | 
  
 | 
        /// <summary> 
 | 
        /// Create a new instance of the <see cref="MemoryCache"/>. 
 | 
        /// </summary> 
 | 
        /// <param name="maximumSize">The maximum allowed number of items in the cache.</param> 
 | 
        /// <param name="reductionSize">The number of items to be deleted per cleanup of the cache.</param> 
 | 
        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<TKey, CacheItem>(); 
 | 
        } 
 | 
  
 | 
        /// <summary> 
 | 
        /// Get the value in the cache for the given key. 
 | 
        /// </summary> 
 | 
        /// <param name="key">The key to lookup in the cache.</param> 
 | 
        /// <returns>The value for the given key.</returns> 
 | 
        public TValue this[TKey key] 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                CacheItem cacheItem = dictionary[key]; 
 | 
                cacheItem.Accessed(); 
 | 
                return cacheItem.Value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        /// <summary> 
 | 
        /// Gets the number of items in the cache. 
 | 
        /// </summary> 
 | 
        public int Count 
 | 
        { 
 | 
            get 
 | 
            { 
 | 
                return dictionary.Count; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        /// <summary> 
 | 
        /// Returns true if an item with the given key is present in the cache. 
 | 
        /// </summary> 
 | 
        /// <param name="key">The key to lookup in the cache.</param> 
 | 
        /// <returns>True if an item is present in the cache for the given key.</returns> 
 | 
        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; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        /// <summary> 
 | 
        /// 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. 
 | 
        /// </summary> 
 | 
        /// <param name="key">The key to lookup in the cache.</param> 
 | 
        /// <param name="valueFactory">The factory to produce the value matching with the key.</param> 
 | 
        /// <returns>The value for the given key.</returns> 
 | 
        public TValue GetOrAdd(TKey key, Func<TKey, TValue> 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; 
 | 
        } 
 | 
  
 | 
        /// <summary> 
 | 
        /// 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. 
 | 
        /// </summary> 
 | 
        private void EnsureCacheStorageAvailable() 
 | 
        { 
 | 
            if (dictionary.Count >= maximumSize) // >= because we want to add an item after this method 
 | 
            { 
 | 
                IList<TKey> 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<TKey, TValue> cache; 
 | 
  
 | 
            public CacheItem(MemoryCache<TKey, TValue> 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); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
} 
 |