| 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); | 
|             } | 
|         } | 
|     } | 
| } |