using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
namespace ClosedXML.Excel.Caching
{
internal abstract class XLRepositoryBase : IXLRepository
{
public abstract void Clear();
}
internal abstract class XLRepositoryBase<Tkey, Tvalue> : XLRepositoryBase, IXLRepository<Tkey, Tvalue>
where Tkey : struct, IEquatable<Tkey>
where Tvalue : class
{
const int CONCURRENCY_LEVEL = 4;
const int INITIAL_CAPACITY = 1000;
private readonly ConcurrentDictionary<Tkey, WeakReference> _storage;
private readonly Func<Tkey, Tvalue> _createNew;
protected XLRepositoryBase(Func<Tkey, Tvalue> createNew)
: this(createNew, EqualityComparer<Tkey>.Default)
{
}
protected XLRepositoryBase(Func<Tkey, Tvalue> createNew, IEqualityComparer<Tkey> comparer)
{
_storage = new ConcurrentDictionary<Tkey, WeakReference>(CONCURRENCY_LEVEL, INITIAL_CAPACITY, comparer);
_createNew = createNew;
}
/// <summary>
/// Check if the specified key is presented in the repository.
/// </summary>
/// <param name="key">Key to look for.</param>
/// <param name="value">Value from the repository stored under specified key or null if key does
/// not exist or the entry under this key has already bee GCed.</param>
/// <returns>True if entry exists and alive, false otherwise.</returns>
public bool ContainsKey(Tkey key, out Tvalue value)
{
if (_storage.TryGetValue(key, out WeakReference cachedReference))
{
value = cachedReference.Target as Tvalue;
return (value != null);
}
value = null;
return false;
}
/// <summary>
/// Put the entity into the repository under the specified key if no other entity with
/// the same key is presented.
/// </summary>
/// <param name="key">Key to identify the entity.</param>
/// <param name="value">Entity to store.</param>
/// <returns>Entity that is stored in the repository under the specified key
/// (it can be either the <paramref name="value"/> or another entity that has been added to
/// the repository before.)</returns>
public Tvalue Store(Tkey key, Tvalue value)
{
if (value == null)
return null;
if (!_storage.ContainsKey(key))
{
_storage.TryAdd(key, new WeakReference(value));
return value;
}
else
{
var cachedReference = _storage[key];
var storedValue = cachedReference.Target as Tvalue;
if (storedValue == null)
{
_storage.TryAdd(key, new WeakReference(value));
return value;
}
return storedValue;
}
}
public Tvalue GetOrCreate(Tkey key)
{
if (_storage.TryGetValue(key, out WeakReference cachedReference))
{
var storedValue = cachedReference.Target as Tvalue;
if (storedValue != null)
{
return storedValue;
}
else
{
_storage.TryRemove(key, out WeakReference _);
}
}
var value = _createNew(key);
return Store(key, value);
}
public Tvalue Replace(Tkey oldKey, Tkey newKey)
{
WeakReference cachedReference;
_storage.TryRemove(oldKey, out cachedReference);
if (cachedReference != null)
{
_storage.TryAdd(newKey, cachedReference);
return GetOrCreate(newKey);
}
return null;
}
public void Remove(Tkey key)
{
WeakReference _;
_storage.TryRemove(key, out _);
}
public override void Clear()
{
_storage.Clear();
}
/// <summary>
/// Enumerate items in repository removing "dead" entries.
/// </summary>
public IEnumerator<Tvalue> GetEnumerator()
{
return _storage
.Select(pair =>
{
var val = pair.Value.Target as Tvalue;
if (val == null)
{
_storage.TryRemove(pair.Key, out WeakReference _);
}
return val;
})
.Where(val => val != null)
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}